feature: Default theme for new users (#2898)

This commit is contained in:
Arc 2025-01-28 09:29:38 +00:00 committed by GitHub
parent eeebaffbed
commit cc33a49b12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 211 additions and 106 deletions

View file

@ -153,6 +153,54 @@
<br />
</div>
</div>
<p><span v-text="$t('ui_default_theme')"></span></p>
<div class="row q-col-gutter-md">
<div class="col">
<q-select
v-model="formData.lnbits_default_border"
:options="globalBorderOptions"
label="Border"
@update:model-value="applyGlobalBorder"
>
</q-select>
</div>
<div class="col">
<q-select
v-model="formData.lnbits_default_theme"
:options="lnbits_theme_options"
label="Theme"
@update:model-value="applyGlobalTheme"
>
</q-select>
</div>
<div class="col">
<q-select
v-model="formData.lnbits_default_reaction"
:options="reactionOptions"
label="Payment reaction"
@update:model-value="applyGlobalReaction"
>
</q-select>
</div>
<div class="col">
<q-input
type="text"
v-model="formData.lnbits_default_bgimage"
label="Background Image"
@update:model-value="applyGlobalBgimage"
>
</q-input>
</div>
<div class="col">
<q-toggle
type="bool"
v-model="formData.lnbits_default_gradient"
color="primary"
label="Gradient Background"
>
</q-toggle>
</div>
</div>
</div>
</q-card-section>
</q-tab-panel>

View file

@ -337,7 +337,7 @@
v-if="g.allowedThemes.includes('classic')"
dense
flat
@click="changeColor('classic')"
@click="themeChoiceFunc('classic')"
icon="circle"
color="deep-purple"
size="md"
@ -347,7 +347,7 @@
v-if="g.allowedThemes.includes('bitcoin')"
dense
flat
@click="changeColor('bitcoin')"
@click="themeChoiceFunc('bitcoin')"
icon="circle"
color="orange"
size="md"
@ -357,7 +357,7 @@
v-if="g.allowedThemes.includes('mint')"
dense
flat
@click="changeColor('mint')"
@click="themeChoiceFunc('mint')"
icon="circle"
color="green"
size="md"
@ -366,7 +366,7 @@
v-if="g.allowedThemes.includes('autumn')"
dense
flat
@click="changeColor('autumn')"
@click="themeChoiceFunc('autumn')"
icon="circle"
color="brown"
size="md"
@ -376,7 +376,7 @@
v-if="g.allowedThemes.includes('monochrome')"
dense
flat
@click="changeColor('monochrome')"
@click="themeChoiceFunc('monochrome')"
icon="circle"
color="grey"
size="md"
@ -386,7 +386,7 @@
v-if="g.allowedThemes.includes('salvador')"
dense
flat
@click="changeColor('salvador')"
@click="themeChoiceFunc('salvador')"
icon="circle"
color="blue-10"
size="md"
@ -396,7 +396,7 @@
v-if="g.allowedThemes.includes('freedom')"
dense
flat
@click="changeColor('freedom')"
@click="themeChoiceFunc('freedom')"
icon="circle"
color="pink-13"
size="md"
@ -406,7 +406,7 @@
v-if="g.allowedThemes.includes('cyber')"
dense
flat
@click="changeColor('cyber')"
@click="themeChoiceFunc('cyber')"
icon="circle"
color="light-green-9"
size="md"
@ -416,7 +416,7 @@
v-if="g.allowedThemes.includes('flamingo')"
dense
flat
@click="changeColor('flamingo')"
@click="themeChoiceFunc('flamingo')"
icon="circle"
color="pink-3"
size="md"
@ -430,9 +430,9 @@
</div>
<div class="col-8">
<q-input
v-model="backgroundImage"
v-model="bgimageSelection"
:label="$t('background_image')"
@update:model-value="backgroundImageFunc"
@update:model-value="bgimageChoiceFunc"
>
<q-tooltip
><span v-text="$t('background_image')"></span
@ -486,9 +486,9 @@
</div>
<div class="col-8">
<q-select
v-model="borderChoice"
v-model="borderSelection"
:options="borderOptions"
label="Reactions"
label="Borders"
@update:model-value="applyBorder"
>
<q-tooltip
@ -514,7 +514,7 @@
</div>
<div class="col-8">
<q-select
v-model="reactionChoice"
v-model="reactionSelection"
:options="reactionOptions"
label="Reactions"
@update:model-value="reactionChoiceFunc"

View file

@ -24,6 +24,7 @@
:style="$q.screen.lt.md ? {
background: $q.screen.lt.md ? 'none !important': ''
, boxShadow: $q.screen.lt.md ? 'none !important': ''
, border: $q.screen.lt.md ? 'none !important': ''
, width: $q.screen.lt.md && mobileSimple ? '90% !important': ''
} : ''"
>
@ -180,6 +181,7 @@
? {
background: $q.screen.lt.md ? 'none !important' : '',
boxShadow: $q.screen.lt.md ? 'none !important' : '',
border: $q.screen.lt.md ? 'none !important': '',
marginTop: $q.screen.lt.md ? '0px !important' : ''
}
: ''

View file

@ -101,6 +101,21 @@ def template_renderer(additional_folders: Optional[list] = None) -> Jinja2Templa
if settings.lnbits_custom_logo:
t.env.globals["USE_CUSTOM_LOGO"] = settings.lnbits_custom_logo
if settings.lnbits_default_reaction:
t.env.globals["USE_DEFAULT_REACTION"] = settings.lnbits_default_reaction
if settings.lnbits_default_theme:
t.env.globals["USE_DEFAULT_THEME"] = settings.lnbits_default_theme
if settings.lnbits_default_border:
t.env.globals["USE_DEFAULT_BORDER"] = settings.lnbits_default_border
if settings.lnbits_default_gradient:
t.env.globals["USE_DEFAULT_GRADIENT"] = settings.lnbits_default_gradient
if settings.lnbits_default_bgimage:
t.env.globals["USE_DEFAULT_BGIMAGE"] = settings.lnbits_default_bgimage
if settings.bundle_assets:
t.env.globals["INCLUDED_JS"] = ["bundle.min.js"]
t.env.globals["INCLUDED_CSS"] = ["bundle.min.css"]

View file

@ -258,6 +258,11 @@ class ThemesSettings(LNbitsSettings):
lnbits_allowed_currencies: list[str] = Field(default=[])
lnbits_default_accounting_currency: Optional[str] = Field(default=None)
lnbits_qr_logo: str = Field(default="/static/images/logos/lnbits.png")
lnbits_default_reaction: str = Field(default="confettiBothSides")
lnbits_default_theme: str = Field(default="classic")
lnbits_default_border: str = Field(default="hard-border")
lnbits_default_gradient: bool = Field(default=False)
lnbits_default_bgimage: str = Field(default=None)
class OpsSettings(LNbitsSettings):

File diff suppressed because one or more lines are too long

View file

@ -450,6 +450,7 @@ window.localisation.en = {
ui_site_description: 'Site Description',
ui_site_description_hint: 'Use plain text, Markdown, or raw HTML',
ui_default_wallet_name: 'Default Wallet Name',
ui_default_theme: 'Default Theme',
lnbits_wallet: 'LNbits wallet',
denomination: 'Denomination',
denomination_hint: 'The name for the FakeWallet token',

View file

@ -91,40 +91,34 @@ window.AccountPageLogic = {
window.i18n.global.locale = newValue
this.$q.localStorage.set('lnbits.lang', newValue)
},
toggleDarkMode() {
this.$q.dark.toggle()
this.$q.localStorage.set('lnbits.darkMode', this.$q.dark.isActive)
if (!this.$q.dark.isActive && this.gradientChoice) {
this.toggleGradient()
}
},
toggleGradient() {
this.gradientChoice = !this.gradientChoice
this.gradientSelection = !this.gradientChoice
this.gradientChoice = this.gradientSelection
this.$q.localStorage.set('lnbits.backgroundImage', 'none')
this.applyGradient()
this.$q.localStorage.set('lnbits.backgroundImage', '')
this.applyBorder()
if (!this.gradientChoice) {
window.location.reload()
}
},
reactionChoiceFunc() {
this.$q.localStorage.set('lnbits.reactions', this.reactionChoice)
this.$q.localStorage.set('lnbits.reactions', this.reactionSelection)
this.reactionChoice = this.reactionSelection
},
backgroundImageFunc() {
this.$q.localStorage.set('lnbits.backgroundImage', this.backgroundImage)
bgimageChoiceFunc() {
this.$q.localStorage.set('lnbits.backgroundImage', this.bgimageSelection)
this.bgimageChoice = this.bgimageSelection
this.applyBackgroundImage()
},
changeColor(newValue) {
document.body.setAttribute('data-theme', newValue)
this.$q.localStorage.set('lnbits.theme', newValue)
themeChoiceFunc(newValue) {
this.changeTheme(newValue)
this.setColors()
this.applyBorder()
if (this.$q.localStorage.getItem('lnbits.gradientBg')) {
if (this.gradientChoice) {
this.applyGradient()
}
if (this.$q.localStorage.getItem('lnbits.backgroundImage')) {
if (this.bgimageChoice) {
this.applyBackgroundImage()
}
this.applyBorder()
},
async updateAccount() {
try {
@ -432,6 +426,10 @@ window.AccountPageLogic = {
}
},
async created() {
this.borderSelection = this.borderChoice
this.reactionSelection = this.reactionChoice
this.bgimageSelection = this.bgimageChoice
this.themeSelection = this.themeChoice
try {
const {data} = await LNbits.api.getAuthenticatedUser()
this.user = data

View file

@ -16,6 +16,19 @@ window.AdminPageLogic = {
'monochrome',
'salvador'
],
reactionOptions: [
'none',
'confettiBothSides',
'confettiFireworks',
'confettiStars',
'confettiTop'
],
globalBorderOptions: [
'retro-border',
'hard-border',
'neon-border',
'no-border'
],
auditData: {},
statusData: {},
statusDataTable: {

View file

@ -465,10 +465,27 @@ window.windowMixin = {
updatePaymentsHash: '',
mobileSimple: true,
walletFlip: true,
reactionChoice: 'confettiTop',
borderChoice: '',
gradientChoice:
this.$q.localStorage.getItem('lnbits.gradientBg') || false,
borderSelection: null,
gradientSelection: null,
themeSelection: null,
reactionSelection: null,
bgimageSelection: null,
gradientSelection: false,
borderChoice: this.$q.localStorage.has('lnbits.border')
? this.$q.localStorage.getItem('lnbits.border')
: USE_DEFAULT_BORDER,
gradientChoice: this.$q.localStorage.has('lnbits.gradientBg')
? this.$q.localStorage.getItem('lnbits.gradientBg')
: USE_DEFAULT_GRADIENT,
themeChoice: this.$q.localStorage.has('lnbits.theme')
? this.$q.localStorage.getItem('lnbits.theme')
: USE_DEFAULT_THEME,
reactionChoice: this.$q.localStorage.has('lnbits.reactions')
? this.$q.localStorage.getItem('lnbits.reactions')
: USE_DEFAULT_REACTION,
bgimageChoice: this.$q.localStorage.has('lnbits.backgroundImage')
? this.$q.localStorage.getItem('lnbits.backgroundImage')
: USE_DEFAULT_BGIMAGE,
isUserAuthorized: false,
walletEventListeners: [],
backgroundImage: ''
@ -528,7 +545,6 @@ window.windowMixin = {
},
selectWallet(wallet) {
Object.assign(this.g.wallet, wallet)
// this.wallet = this.g.wallet
this.updatePayments = !this.updatePayments
this.balance = parseInt(wallet.balance_msat / 1000)
const currentPath = this.$route.path
@ -543,15 +559,24 @@ window.windowMixin = {
window.history.replaceState({}, '', url.toString())
}
},
changeColor(newValue) {
changeTheme(newValue) {
document.body.setAttribute('data-theme', newValue)
this.$q.localStorage.set('lnbits.theme', newValue)
if (this.themeSelection) {
this.$q.localStorage.set('lnbits.theme', newValue)
}
this.setColors()
},
applyGradient() {
darkBgColor = this.$q.localStorage.getItem('lnbits.darkBgColor')
primaryColor = this.$q.localStorage.getItem('lnbits.primaryColor')
if (this.gradientChoice) {
this.$q.localStorage.set('lnbits.gradientBg', true)
if (!this.$q.dark.isActive) {
this.$q.dark.toggle()
this.$q.localStorage.set('lnbits.darkMode', true)
}
if (this.gradientSelection) {
this.$q.localStorage.set('lnbits.gradientBg', true)
}
const gradientStyle = `linear-gradient(to bottom right, ${LNbits.utils.hexDarken(String(primaryColor), -70)}, #0a0a0a)`
document.body.style.setProperty(
'background-image',
@ -561,36 +586,50 @@ window.windowMixin = {
const gradientStyleCards = `background-color: ${LNbits.utils.hexAlpha(String(darkBgColor), 0.4)} !important`
const style = document.createElement('style')
style.innerHTML =
`body[data-theme="${this.$q.localStorage.getItem('lnbits.theme')}"] .q-card:not(.q-dialog .q-card, .lnbits__dialog-card, .q-dialog-plugin--dark), body.body${this.$q.dark.isActive ? '--dark' : ''} .q-header, body.body${this.$q.dark.isActive ? '--dark' : ''} .q-drawer { ${gradientStyleCards} }` +
`body[data-theme="${this.$q.localStorage.getItem('lnbits.theme')}"].body--dark{background: ${LNbits.utils.hexDarken(String(primaryColor), -88)} !important; }` +
`[data-theme="${this.$q.localStorage.getItem('lnbits.theme')}"] .q-card--dark{background: ${String(darkBgColor)} !important;} }`
`body[data-theme="${this.themeChoice}"] .q-card:not(.q-dialog .q-card, .lnbits__dialog-card, .q-dialog-plugin--dark), body.body${this.$q.dark.isActive ? '--dark' : ''} .q-header, body.body${this.$q.dark.isActive ? '--dark' : ''} .q-drawer { ${gradientStyleCards} }` +
`body[data-theme="${this.themeChoice}"].body--dark{background: ${LNbits.utils.hexDarken(String(primaryColor), -88)} !important; }` +
`[data-theme="${this.themeChoice}"] .q-card--dark{background: ${String(darkBgColor)} !important;} }`
document.head.appendChild(style)
} else {
this.$q.localStorage.set('lnbits.gradientBg', false)
if (this.gradientSelection) {
this.$q.localStorage.set('lnbits.gradientBg', false)
}
}
if (!this.$q.dark.isActive) {
this.toggleDarkMode()
},
toggleDarkMode() {
this.$q.dark.toggle()
this.$q.localStorage.set('lnbits.darkMode', this.$q.dark.isActive)
if (this.gradientChoice && !this.$q.dark.isActive) {
window.location.reload()
}
},
applyBackgroundImage() {
if (this.backgroundImage) {
this.$q.localStorage.set('lnbits.backgroundImage', this.backgroundImage)
this.gradientChoice = true
this.applyGradient()
if (this.bgimageSelection) {
this.$q.localStorage.set(
'lnbits.backgroundImage',
this.bgimageSelection
)
this.bgimageChoice = this.bgimageSelection
}
let bgImage = this.$q.localStorage.getItem('lnbits.backgroundImage')
if (bgImage) {
this.backgroundImage = bgImage
if (
this.bgimageChoice &&
this.bgimageChoice !== 'null' &&
this.bgimageChoice !== ''
) {
if (!this.gradientChoice) {
this.gradientChoice = true
this.applyGradient()
}
const style = document.createElement('style')
style.innerHTML = `
body[data-theme="${this.$q.localStorage.getItem('lnbits.theme')}"]::before {
body[data-theme="${this.themeChoice}"]::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: url(${bgImage});
background: url(${this.bgimageChoice});
background-size: cover;
filter: blur(8px);
z-index: -1;
@ -598,38 +637,39 @@ window.windowMixin = {
background-repeat: no-repeat;
background-size: cover;
}
body[data-theme="${this.$q.localStorage.getItem('lnbits.theme')}"] .q-page-container {
body[data-theme="${this.themeChoice}"] .q-page-container {
backdrop-filter: none; /* Ensure the page content is not affected */
}`
document.head.appendChild(style)
}
},
applyBorder() {
if (this.borderChoice) {
this.$q.localStorage.setItem('lnbits.border', this.borderChoice)
if (this.borderSelection) {
this.$q.localStorage.setItem('lnbits.border', this.borderSelection)
this.borderChoice = this.$q.localStorage.getItem('lnbits.border')
}
let borderStyle = this.$q.localStorage.getItem('lnbits.border')
if (!borderStyle) {
this.$q.localStorage.set('lnbits.border', 'hard-border')
borderStyle = 'hard-border'
}
this.borderChoice = borderStyle
let borderStyleCSS
if (borderStyle == 'hard-border') {
if (this.borderChoice == 'hard-border') {
borderStyleCSS = `box-shadow: 0 0 0 1px rgba(0,0,0,.12), 0 0 0 1px #ffffff47; border: none;`
}
if (borderStyle == 'neon-border') {
if (this.borderChoice == 'neon-border') {
borderStyleCSS = `border: 2px solid ${this.$q.localStorage.getItem('lnbits.primaryColor')}; box-shadow: none;`
}
if (borderStyle == 'no-border') {
if (this.borderChoice == 'no-border') {
borderStyleCSS = `box-shadow: none; border: none;`
}
if (borderStyle == 'retro-border') {
if (this.borderChoice == 'retro-border') {
borderStyleCSS = `border: none; border-color: rgba(255, 255, 255, 0.28); box-shadow: 0 1px 5px rgba(255, 255, 255, 0.2), 0 2px 2px rgba(255, 255, 255, 0.14), 0 3px 1px -2px rgba(255, 255, 255, 0.12);`
}
let style = document.createElement('style')
style.innerHTML = `body[data-theme="${this.$q.localStorage.getItem('lnbits.theme')}"] .q-card.q-card--dark, .q-date--dark { ${borderStyleCSS} }`
style.innerHTML = `
body[data-theme="${this.themeChoice}"] .q-card,
body[data-theme="${this.themeChoice}"] .q-card.q-card--dark,
body[data-theme="${this.themeChoice}"] .q-date,
body[data-theme="${this.themeChoice}"] .q-date--dark {
${borderStyleCSS}
}
`
document.head.appendChild(style)
},
setColors() {
@ -764,18 +804,18 @@ window.windowMixin = {
}
},
async created() {
if (
this.$q.localStorage.getItem('lnbits.darkMode') == true ||
this.$q.localStorage.getItem('lnbits.darkMode') == false
) {
this.$q.dark.set(this.$q.localStorage.getItem('lnbits.darkMode'))
} else {
this.$q.dark.set(true)
}
this.reactionChoice =
this.$q.localStorage.getItem('lnbits.reactions') || 'confettiTop'
this.g.allowedThemes = window.allowedThemes ?? ['bitcoin']
this.$q.dark.set(
this.$q.localStorage.has('lnbits.darkMode')
? this.$q.localStorage.getItem('lnbits.darkMode')
: true
)
this.changeTheme(this.themeChoice)
this.applyBorder()
if (this.$q.dark.isActive) {
this.applyGradient()
}
this.applyBackgroundImage()
let locale = this.$q.localStorage.getItem('lnbits.lang')
if (locale) {
@ -793,28 +833,6 @@ window.windowMixin = {
this.g.offline = false
})
// failsafe if admin changes themes halfway
if (!this.$q.localStorage.getItem('lnbits.theme')) {
this.changeColor(this.g.allowedThemes[0])
}
if (
this.$q.localStorage.getItem('lnbits.theme') &&
!this.g.allowedThemes.includes(
this.$q.localStorage.getItem('lnbits.theme')
)
) {
this.changeColor(this.g.allowedThemes[0])
}
if (this.$q.localStorage.getItem('lnbits.theme')) {
document.body.setAttribute(
'data-theme',
this.$q.localStorage.getItem('lnbits.theme')
)
}
this.applyGradient()
this.applyBackgroundImage()
this.applyBorder()
if (window.user) {
this.g.user = Vue.reactive(window.LNbits.map.user(window.user))
}

View file

@ -217,7 +217,7 @@
style="text-decoration: none"
:style="
g.wallet && g.wallet.id === wallet.id
? `border: 1px solid ${primaryColor}; width: 250px; text-decoration: none; cursor: pointer;`
? `width: 250px; text-decoration: none; cursor: pointer;`
: 'width: 250px; text-decoration: none; border: 0px; cursor: pointer;'
"
:class="{
@ -326,6 +326,11 @@
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())
}