Compare commits

...

3 commits

13 changed files with 326 additions and 236 deletions

0
preview.style.js Normal file
View file

View file

@ -13,8 +13,7 @@ import VBodyScrollLock from 'src/directives/body_scroll_lock'
import { windowWidth, windowHeight } from '../services/window_utils/window_utils' import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js' import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' import { applyConfig } from '../services/style_setter/style_setter.js'
import { applyTheme, applyConfig, tryLoadCache } from '../services/style_setter/style_setter.js'
import FaviconService from '../services/favicon_service/favicon_service.js' import FaviconService from '../services/favicon_service/favicon_service.js'
import { initServiceWorker, updateFocus } from '../services/sw/sw.js' import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
@ -160,8 +159,6 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('showFeaturesPanel') copyInstanceOption('showFeaturesPanel')
copyInstanceOption('hideSitename') copyInstanceOption('hideSitename')
copyInstanceOption('sidebarRight') copyInstanceOption('sidebarRight')
return store.dispatch('setTheme', config.theme)
} }
const getTOS = async ({ store }) => { const getTOS = async ({ store }) => {
@ -352,29 +349,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
store.dispatch('setInstanceOption', { name: 'server', value: server }) store.dispatch('setInstanceOption', { name: 'server', value: server })
await setConfig({ store }) await setConfig({ store })
await store.dispatch('setTheme')
const { customTheme, customThemeSource, forceThemeRecompilation, themeDebug } = store.state.config
const { theme } = store.state.instance
const customThemePresent = customThemeSource || customTheme
console.log('DEBUG INITIAL', themeDebug, forceThemeRecompilation)
if (!forceThemeRecompilation && !themeDebug && tryLoadCache()) {
store.commit('setThemeApplied')
} else {
if (customThemePresent) {
if (customThemeSource && customThemeSource.themeEngineVersion === CURRENT_VERSION) {
applyTheme(customThemeSource, () => {}, themeDebug)
} else {
applyTheme(customTheme, () => {}, themeDebug)
}
store.commit('setThemeApplied')
} else if (theme) {
// do nothing, it will load asynchronously
} else {
console.error('Failed to load any theme!')
}
}
applyConfig(store.state.config) applyConfig(store.state.config)

View file

@ -29,6 +29,11 @@ const AppearanceTab = {
key: mode, key: mode,
value: i - 1, value: i - 1,
label: this.$t(`settings.forced_roundness_mode_${mode}`) label: this.$t(`settings.forced_roundness_mode_${mode}`)
})),
underlayOverrideModes: ['none', 'opaque', 'transparent'].map((mode, i) => ({
key: mode,
value: mode,
label: this.$t(`settings.underlay_override_mode_${mode}`)
})) }))
} }
}, },

View file

@ -182,6 +182,15 @@
{{ $t('settings.force_interface_roundness') }} {{ $t('settings.force_interface_roundness') }}
</ChoiceSetting> </ChoiceSetting>
</li> </li>
<li>
<ChoiceSetting
id="underlayOverride"
path="theme3hacks.underlay"
:options="underlayOverrideModes"
>
{{ $t('settings.underlay_overrides') }}
</ChoiceSetting>
</li>
<li v-if="instanceWallpaperUsed"> <li v-if="instanceWallpaperUsed">
<BooleanSetting path="hideInstanceWallpaper"> <BooleanSetting path="hideInstanceWallpaper">
{{ $t('settings.hide_wallpaper') }} {{ $t('settings.hide_wallpaper') }}

View file

@ -1,7 +1,8 @@
import { import {
rgb2hex, rgb2hex,
hex2rgb, hex2rgb,
getContrastRatioLayers getContrastRatioLayers,
relativeLuminance
} from 'src/services/color_convert/color_convert.js' } from 'src/services/color_convert/color_convert.js'
import { import {
getThemes getThemes
@ -23,10 +24,14 @@ import {
generateShadows, generateShadows,
generateRadii, generateRadii,
generateFonts, generateFonts,
composePreset,
shadows2to3, shadows2to3,
colors2to3 colors2to3
} from 'src/services/theme_data/theme_data.service.js' } from 'src/services/theme_data/theme_data.service.js'
import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
import { init } from 'src/services/theme_data/theme_data_3.service.js'
import { getCssRules } from 'src/services/theme_data/css_utils.js'
import ColorInput from 'src/components/color_input/color_input.vue' import ColorInput from 'src/components/color_input/color_input.vue'
import RangeInput from 'src/components/range_input/range_input.vue' import RangeInput from 'src/components/range_input/range_input.vue'
import OpacityInput from 'src/components/opacity_input/opacity_input.vue' import OpacityInput from 'src/components/opacity_input/opacity_input.vue'
@ -62,6 +67,7 @@ const colorConvert = (color) => {
export default { export default {
data () { data () {
return { return {
themeV3Preview: [],
themeImporter: newImporter({ themeImporter: newImporter({
validator: this.importValidator, validator: this.importValidator,
onImport: this.onImport, onImport: this.onImport,
@ -78,10 +84,7 @@ export default {
tempImportFile: undefined, tempImportFile: undefined,
engineVersion: 0, engineVersion: 0,
previewShadows: {}, previewTheme: {},
previewColors: {},
previewRadii: {},
previewFonts: {},
shadowsInvalid: true, shadowsInvalid: true,
colorsInvalid: true, colorsInvalid: true,
@ -232,13 +235,6 @@ export default {
chatMessage: this.chatMessageRadiusLocal chatMessage: this.chatMessageRadiusLocal
} }
}, },
preview () {
return composePreset(this.previewColors, this.previewRadii, this.previewShadows, this.previewFonts)
},
previewTheme () {
if (!this.preview.theme.colors) return { colors: {}, opacity: {}, radii: {}, shadows: {}, fonts: {} }
return this.preview.theme
},
// This needs optimization maybe // This needs optimization maybe
previewContrast () { previewContrast () {
try { try {
@ -306,14 +302,6 @@ export default {
return {} return {}
} }
}, },
previewRules () {
if (!this.preview.rules) return ''
return [
...Object.values(this.preview.rules),
'color: var(--text)',
'font-family: var(--interfaceFont, sans-serif)'
].join(';')
},
shadowsAvailable () { shadowsAvailable () {
return Object.keys(DEFAULT_SHADOWS).sort() return Object.keys(DEFAULT_SHADOWS).sort()
}, },
@ -514,6 +502,7 @@ export default {
this.$store.dispatch('setOption', { this.$store.dispatch('setOption', {
name: 'customTheme', name: 'customTheme',
value: { value: {
ignore: true,
themeFileVersion: this.selectedVersion, themeFileVersion: this.selectedVersion,
themeEngineVersion: CURRENT_VERSION, themeEngineVersion: CURRENT_VERSION,
...this.previewTheme ...this.previewTheme
@ -532,16 +521,24 @@ export default {
} }
}) })
}, },
updatePreviewColorsAndShadows () { updatePreviewColors () {
this.previewColors = generateColors({ const result = generateColors({
opacity: this.currentOpacity, opacity: this.currentOpacity,
colors: this.currentColors colors: this.currentColors
}) })
this.previewShadows = generateShadows( this.previewTheme.colors = result.theme.colors
{ shadows: this.shadowsLocal, opacity: this.previewTheme.opacity, themeEngineVersion: this.engineVersion }, this.previewTheme.opacity = result.theme.opacity
this.previewColors.theme.colors, },
this.previewColors.mod updatePreviewShadows () {
) this.previewTheme.shadows = generateShadows(
{
shadows: this.shadowsLocal,
opacity: this.previewTheme.opacity,
themeEngineVersion: this.engineVersion
},
this.previewTheme.colors,
relativeLuminance(this.previewTheme.colors.bg) < 0.5 ? 1 : -1
).theme.shadows
}, },
importTheme () { this.themeImporter.importData() }, importTheme () { this.themeImporter.importData() },
exportTheme () { this.themeExporter.exportData() }, exportTheme () { this.themeExporter.exportData() },
@ -692,6 +689,8 @@ export default {
} else { } else {
this.shadowsLocal = shadows this.shadowsLocal = shadows
} }
this.updatePreviewColors()
this.updatePreviewShadows()
this.shadowSelected = this.shadowsAvailable[0] this.shadowSelected = this.shadowsAvailable[0]
} }
@ -699,12 +698,32 @@ export default {
this.clearFonts() this.clearFonts()
this.fontsLocal = fonts this.fontsLocal = fonts
} }
},
updateTheme3Preview () {
const theme2 = convertTheme2To3(this.previewTheme)
const theme3 = init({
inputRuleset: theme2,
ultimateBackgroundColor: '#000000',
liteMode: true
})
this.themeV3Preview = getCssRules(theme3.eager)
.map(x => {
if (x.startsWith('html')) {
return x.replace('html', '#theme-preview')
} else if (x.startsWith('#content')) {
return x.replace('#content', '#theme-preview')
} else {
return '#theme-preview > ' + x
}
})
.join('\n')
} }
}, },
watch: { watch: {
currentRadii () { currentRadii () {
try { try {
this.previewRadii = generateRadii({ radii: this.currentRadii }) this.previewTheme.radii = generateRadii({ radii: this.currentRadii }).theme.radii
this.radiiInvalid = false this.radiiInvalid = false
} catch (e) { } catch (e) {
this.radiiInvalid = true this.radiiInvalid = true
@ -713,9 +732,8 @@ export default {
}, },
shadowsLocal: { shadowsLocal: {
handler () { handler () {
if (Object.getOwnPropertyNames(this.previewColors).length === 1) return
try { try {
this.updatePreviewColorsAndShadows() this.updatePreviewShadows()
this.shadowsInvalid = false this.shadowsInvalid = false
} catch (e) { } catch (e) {
this.shadowsInvalid = true this.shadowsInvalid = true
@ -727,7 +745,7 @@ export default {
fontsLocal: { fontsLocal: {
handler () { handler () {
try { try {
this.previewFonts = generateFonts({ fonts: this.fontsLocal }) this.previewTheme.fonts = generateFonts({ fonts: this.fontsLocal }).theme.fonts
this.fontsInvalid = false this.fontsInvalid = false
} catch (e) { } catch (e) {
this.fontsInvalid = true this.fontsInvalid = true
@ -738,18 +756,16 @@ export default {
}, },
currentColors () { currentColors () {
try { try {
this.updatePreviewColorsAndShadows() this.updatePreviewColors()
this.colorsInvalid = false this.colorsInvalid = false
this.shadowsInvalid = false
} catch (e) { } catch (e) {
this.colorsInvalid = true this.colorsInvalid = true
this.shadowsInvalid = true
console.warn(e) console.warn(e)
} }
}, },
currentOpacity () { currentOpacity () {
try { try {
this.updatePreviewColorsAndShadows() this.updatePreviewColors()
} catch (e) { } catch (e) {
console.warn(e) console.warn(e)
} }

View file

@ -1,5 +1,8 @@
<template> <template>
<div class="theme-tab"> <div class="theme-tab">
<div class="alert warning">
{{ $t("settings.style.themes2_outdated") }}
</div>
<div class="presets-container"> <div class="presets-container">
<div class="save-load"> <div class="save-load">
<div <div
@ -120,7 +123,19 @@
</div> </div>
</div> </div>
<preview :style="previewRules" /> <!-- eslint-disable vue/no-v-text-v-html-on-component -->
<component :is="'style'" v-html="themeV3Preview"/>
<!-- eslint-enable vue/no-v-text-v-html-on-component -->
<preview id="theme-preview"/>
<div>
<button
class="btn button-default"
@click="updateTheme3Preview"
>
{{ $t("settings.style.update_preview") }}
</button>
</div>
<keep-alive> <keep-alive>
<tab-switcher key="style-tweak"> <tab-switcher key="style-tweak">
@ -156,7 +171,7 @@
<OpacityInput <OpacityInput
v-model="bgOpacityLocal" v-model="bgOpacityLocal"
name="bgOpacity" name="bgOpacity"
:fallback="previewTheme.opacity.bg" :fallback="previewTheme.opacity?.bg"
/> />
<ColorInput <ColorInput
v-model="textColorLocal" v-model="textColorLocal"
@ -167,14 +182,14 @@
<ColorInput <ColorInput
v-model="accentColorLocal" v-model="accentColorLocal"
name="accentColor" name="accentColor"
:fallback="previewTheme.colors.link" :fallback="previewTheme.colors?.link"
:label="$t('settings.accent')" :label="$t('settings.accent')"
:show-optional-tickbox="typeof linkColorLocal !== 'undefined'" :show-optional-tickbox="typeof linkColorLocal !== 'undefined'"
/> />
<ColorInput <ColorInput
v-model="linkColorLocal" v-model="linkColorLocal"
name="linkColor" name="linkColor"
:fallback="previewTheme.colors.accent" :fallback="previewTheme.colors?.accent"
:label="$t('settings.links')" :label="$t('settings.links')"
:show-optional-tickbox="typeof accentColorLocal !== 'undefined'" :show-optional-tickbox="typeof accentColorLocal !== 'undefined'"
/> />
@ -190,13 +205,13 @@
v-model="fgTextColorLocal" v-model="fgTextColorLocal"
name="fgTextColor" name="fgTextColor"
:label="$t('settings.text')" :label="$t('settings.text')"
:fallback="previewTheme.colors.fgText" :fallback="previewTheme.colors?.fgText"
/> />
<ColorInput <ColorInput
v-model="fgLinkColorLocal" v-model="fgLinkColorLocal"
name="fgLinkColor" name="fgLinkColor"
:label="$t('settings.links')" :label="$t('settings.links')"
:fallback="previewTheme.colors.fgLink" :fallback="previewTheme.colors?.fgLink"
/> />
<p>{{ $t('settings.style.common_colors.foreground_hint') }}</p> <p>{{ $t('settings.style.common_colors.foreground_hint') }}</p>
</div> </div>
@ -256,14 +271,14 @@
<ColorInput <ColorInput
v-model="postLinkColorLocal" v-model="postLinkColorLocal"
name="postLinkColor" name="postLinkColor"
:fallback="previewTheme.colors.accent" :fallback="previewTheme.colors?.accent"
:label="$t('settings.links')" :label="$t('settings.links')"
/> />
<ContrastRatio :contrast="previewContrast.postLink" /> <ContrastRatio :contrast="previewContrast.postLink" />
<ColorInput <ColorInput
v-model="postGreentextColorLocal" v-model="postGreentextColorLocal"
name="postGreentextColor" name="postGreentextColor"
:fallback="previewTheme.colors.cGreen" :fallback="previewTheme.colors?.cGreen"
:label="$t('settings.greentext')" :label="$t('settings.greentext')"
/> />
<ContrastRatio :contrast="previewContrast.postGreentext" /> <ContrastRatio :contrast="previewContrast.postGreentext" />
@ -272,13 +287,13 @@
v-model="alertErrorColorLocal" v-model="alertErrorColorLocal"
name="alertError" name="alertError"
:label="$t('settings.style.advanced_colors.alert_error')" :label="$t('settings.style.advanced_colors.alert_error')"
:fallback="previewTheme.colors.alertError" :fallback="previewTheme.colors?.alertError"
/> />
<ColorInput <ColorInput
v-model="alertErrorTextColorLocal" v-model="alertErrorTextColorLocal"
name="alertErrorText" name="alertErrorText"
:label="$t('settings.text')" :label="$t('settings.text')"
:fallback="previewTheme.colors.alertErrorText" :fallback="previewTheme.colors?.alertErrorText"
/> />
<ContrastRatio <ContrastRatio
:contrast="previewContrast.alertErrorText" :contrast="previewContrast.alertErrorText"
@ -288,13 +303,13 @@
v-model="alertWarningColorLocal" v-model="alertWarningColorLocal"
name="alertWarning" name="alertWarning"
:label="$t('settings.style.advanced_colors.alert_warning')" :label="$t('settings.style.advanced_colors.alert_warning')"
:fallback="previewTheme.colors.alertWarning" :fallback="previewTheme.colors?.alertWarning"
/> />
<ColorInput <ColorInput
v-model="alertWarningTextColorLocal" v-model="alertWarningTextColorLocal"
name="alertWarningText" name="alertWarningText"
:label="$t('settings.text')" :label="$t('settings.text')"
:fallback="previewTheme.colors.alertWarningText" :fallback="previewTheme.colors?.alertWarningText"
/> />
<ContrastRatio <ContrastRatio
:contrast="previewContrast.alertWarningText" :contrast="previewContrast.alertWarningText"
@ -304,13 +319,13 @@
v-model="alertNeutralColorLocal" v-model="alertNeutralColorLocal"
name="alertNeutral" name="alertNeutral"
:label="$t('settings.style.advanced_colors.alert_neutral')" :label="$t('settings.style.advanced_colors.alert_neutral')"
:fallback="previewTheme.colors.alertNeutral" :fallback="previewTheme.colors?.alertNeutral"
/> />
<ColorInput <ColorInput
v-model="alertNeutralTextColorLocal" v-model="alertNeutralTextColorLocal"
name="alertNeutralText" name="alertNeutralText"
:label="$t('settings.text')" :label="$t('settings.text')"
:fallback="previewTheme.colors.alertNeutralText" :fallback="previewTheme.colors?.alertNeutralText"
/> />
<ContrastRatio <ContrastRatio
:contrast="previewContrast.alertNeutralText" :contrast="previewContrast.alertNeutralText"
@ -319,7 +334,7 @@
<OpacityInput <OpacityInput
v-model="alertOpacityLocal" v-model="alertOpacityLocal"
name="alertOpacity" name="alertOpacity"
:fallback="previewTheme.opacity.alert" :fallback="previewTheme.opacity?.alert"
/> />
</div> </div>
<div class="color-item"> <div class="color-item">
@ -328,13 +343,13 @@
v-model="badgeNotificationColorLocal" v-model="badgeNotificationColorLocal"
name="badgeNotification" name="badgeNotification"
:label="$t('settings.style.advanced_colors.badge_notification')" :label="$t('settings.style.advanced_colors.badge_notification')"
:fallback="previewTheme.colors.badgeNotification" :fallback="previewTheme.colors?.badgeNotification"
/> />
<ColorInput <ColorInput
v-model="badgeNotificationTextColorLocal" v-model="badgeNotificationTextColorLocal"
name="badgeNotificationText" name="badgeNotificationText"
:label="$t('settings.text')" :label="$t('settings.text')"
:fallback="previewTheme.colors.badgeNotificationText" :fallback="previewTheme.colors?.badgeNotificationText"
/> />
<ContrastRatio <ContrastRatio
:contrast="previewContrast.badgeNotificationText" :contrast="previewContrast.badgeNotificationText"
@ -346,19 +361,19 @@
<ColorInput <ColorInput
v-model="panelColorLocal" v-model="panelColorLocal"
name="panelColor" name="panelColor"
:fallback="previewTheme.colors.panel" :fallback="previewTheme.colors?.panel"
:label="$t('settings.background')" :label="$t('settings.background')"
/> />
<OpacityInput <OpacityInput
v-model="panelOpacityLocal" v-model="panelOpacityLocal"
name="panelOpacity" name="panelOpacity"
:fallback="previewTheme.opacity.panel" :fallback="previewTheme.opacity?.panel"
:disabled="panelColorLocal === 'transparent'" :disabled="panelColorLocal === 'transparent'"
/> />
<ColorInput <ColorInput
v-model="panelTextColorLocal" v-model="panelTextColorLocal"
name="panelTextColor" name="panelTextColor"
:fallback="previewTheme.colors.panelText" :fallback="previewTheme.colors?.panelText"
:label="$t('settings.text')" :label="$t('settings.text')"
/> />
<ContrastRatio <ContrastRatio
@ -368,7 +383,7 @@
<ColorInput <ColorInput
v-model="panelLinkColorLocal" v-model="panelLinkColorLocal"
name="panelLinkColor" name="panelLinkColor"
:fallback="previewTheme.colors.panelLink" :fallback="previewTheme.colors?.panelLink"
:label="$t('settings.links')" :label="$t('settings.links')"
/> />
<ContrastRatio <ContrastRatio
@ -381,20 +396,20 @@
<ColorInput <ColorInput
v-model="topBarColorLocal" v-model="topBarColorLocal"
name="topBarColor" name="topBarColor"
:fallback="previewTheme.colors.topBar" :fallback="previewTheme.colors?.topBar"
:label="$t('settings.background')" :label="$t('settings.background')"
/> />
<ColorInput <ColorInput
v-model="topBarTextColorLocal" v-model="topBarTextColorLocal"
name="topBarTextColor" name="topBarTextColor"
:fallback="previewTheme.colors.topBarText" :fallback="previewTheme.colors?.topBarText"
:label="$t('settings.text')" :label="$t('settings.text')"
/> />
<ContrastRatio :contrast="previewContrast.topBarText" /> <ContrastRatio :contrast="previewContrast.topBarText" />
<ColorInput <ColorInput
v-model="topBarLinkColorLocal" v-model="topBarLinkColorLocal"
name="topBarLinkColor" name="topBarLinkColor"
:fallback="previewTheme.colors.topBarLink" :fallback="previewTheme.colors?.topBarLink"
:label="$t('settings.links')" :label="$t('settings.links')"
/> />
<ContrastRatio :contrast="previewContrast.topBarLink" /> <ContrastRatio :contrast="previewContrast.topBarLink" />
@ -404,19 +419,19 @@
<ColorInput <ColorInput
v-model="inputColorLocal" v-model="inputColorLocal"
name="inputColor" name="inputColor"
:fallback="previewTheme.colors.input" :fallback="previewTheme.colors?.input"
:label="$t('settings.background')" :label="$t('settings.background')"
/> />
<OpacityInput <OpacityInput
v-model="inputOpacityLocal" v-model="inputOpacityLocal"
name="inputOpacity" name="inputOpacity"
:fallback="previewTheme.opacity.input" :fallback="previewTheme.opacity?.input"
:disabled="inputColorLocal === 'transparent'" :disabled="inputColorLocal === 'transparent'"
/> />
<ColorInput <ColorInput
v-model="inputTextColorLocal" v-model="inputTextColorLocal"
name="inputTextColor" name="inputTextColor"
:fallback="previewTheme.colors.inputText" :fallback="previewTheme.colors?.inputText"
:label="$t('settings.text')" :label="$t('settings.text')"
/> />
<ContrastRatio :contrast="previewContrast.inputText" /> <ContrastRatio :contrast="previewContrast.inputText" />
@ -426,33 +441,33 @@
<ColorInput <ColorInput
v-model="btnColorLocal" v-model="btnColorLocal"
name="btnColor" name="btnColor"
:fallback="previewTheme.colors.btn" :fallback="previewTheme.colors?.btn"
:label="$t('settings.background')" :label="$t('settings.background')"
/> />
<OpacityInput <OpacityInput
v-model="btnOpacityLocal" v-model="btnOpacityLocal"
name="btnOpacity" name="btnOpacity"
:fallback="previewTheme.opacity.btn" :fallback="previewTheme.opacity?.btn"
:disabled="btnColorLocal === 'transparent'" :disabled="btnColorLocal === 'transparent'"
/> />
<ColorInput <ColorInput
v-model="btnTextColorLocal" v-model="btnTextColorLocal"
name="btnTextColor" name="btnTextColor"
:fallback="previewTheme.colors.btnText" :fallback="previewTheme.colors?.btnText"
:label="$t('settings.text')" :label="$t('settings.text')"
/> />
<ContrastRatio :contrast="previewContrast.btnText" /> <ContrastRatio :contrast="previewContrast.btnText" />
<ColorInput <ColorInput
v-model="btnPanelTextColorLocal" v-model="btnPanelTextColorLocal"
name="btnPanelTextColor" name="btnPanelTextColor"
:fallback="previewTheme.colors.btnPanelText" :fallback="previewTheme.colors?.btnPanelText"
:label="$t('settings.style.advanced_colors.panel_header')" :label="$t('settings.style.advanced_colors.panel_header')"
/> />
<ContrastRatio :contrast="previewContrast.btnPanelText" /> <ContrastRatio :contrast="previewContrast.btnPanelText" />
<ColorInput <ColorInput
v-model="btnTopBarTextColorLocal" v-model="btnTopBarTextColorLocal"
name="btnTopBarTextColor" name="btnTopBarTextColor"
:fallback="previewTheme.colors.btnTopBarText" :fallback="previewTheme.colors?.btnTopBarText"
:label="$t('settings.style.advanced_colors.top_bar')" :label="$t('settings.style.advanced_colors.top_bar')"
/> />
<ContrastRatio :contrast="previewContrast.btnTopBarText" /> <ContrastRatio :contrast="previewContrast.btnTopBarText" />
@ -460,27 +475,27 @@
<ColorInput <ColorInput
v-model="btnPressedColorLocal" v-model="btnPressedColorLocal"
name="btnPressedColor" name="btnPressedColor"
:fallback="previewTheme.colors.btnPressed" :fallback="previewTheme.colors?.btnPressed"
:label="$t('settings.background')" :label="$t('settings.background')"
/> />
<ColorInput <ColorInput
v-model="btnPressedTextColorLocal" v-model="btnPressedTextColorLocal"
name="btnPressedTextColor" name="btnPressedTextColor"
:fallback="previewTheme.colors.btnPressedText" :fallback="previewTheme.colors?.btnPressedText"
:label="$t('settings.text')" :label="$t('settings.text')"
/> />
<ContrastRatio :contrast="previewContrast.btnPressedText" /> <ContrastRatio :contrast="previewContrast.btnPressedText" />
<ColorInput <ColorInput
v-model="btnPressedPanelTextColorLocal" v-model="btnPressedPanelTextColorLocal"
name="btnPressedPanelTextColor" name="btnPressedPanelTextColor"
:fallback="previewTheme.colors.btnPressedPanelText" :fallback="previewTheme.colors?.btnPressedPanelText"
:label="$t('settings.style.advanced_colors.panel_header')" :label="$t('settings.style.advanced_colors.panel_header')"
/> />
<ContrastRatio :contrast="previewContrast.btnPressedPanelText" /> <ContrastRatio :contrast="previewContrast.btnPressedPanelText" />
<ColorInput <ColorInput
v-model="btnPressedTopBarTextColorLocal" v-model="btnPressedTopBarTextColorLocal"
name="btnPressedTopBarTextColor" name="btnPressedTopBarTextColor"
:fallback="previewTheme.colors.btnPressedTopBarText" :fallback="previewTheme.colors?.btnPressedTopBarText"
:label="$t('settings.style.advanced_colors.top_bar')" :label="$t('settings.style.advanced_colors.top_bar')"
/> />
<ContrastRatio :contrast="previewContrast.btnPressedTopBarText" /> <ContrastRatio :contrast="previewContrast.btnPressedTopBarText" />
@ -488,52 +503,52 @@
<ColorInput <ColorInput
v-model="btnDisabledColorLocal" v-model="btnDisabledColorLocal"
name="btnDisabledColor" name="btnDisabledColor"
:fallback="previewTheme.colors.btnDisabled" :fallback="previewTheme.colors?.btnDisabled"
:label="$t('settings.background')" :label="$t('settings.background')"
/> />
<ColorInput <ColorInput
v-model="btnDisabledTextColorLocal" v-model="btnDisabledTextColorLocal"
name="btnDisabledTextColor" name="btnDisabledTextColor"
:fallback="previewTheme.colors.btnDisabledText" :fallback="previewTheme.colors?.btnDisabledText"
:label="$t('settings.text')" :label="$t('settings.text')"
/> />
<ColorInput <ColorInput
v-model="btnDisabledPanelTextColorLocal" v-model="btnDisabledPanelTextColorLocal"
name="btnDisabledPanelTextColor" name="btnDisabledPanelTextColor"
:fallback="previewTheme.colors.btnDisabledPanelText" :fallback="previewTheme.colors?.btnDisabledPanelText"
:label="$t('settings.style.advanced_colors.panel_header')" :label="$t('settings.style.advanced_colors.panel_header')"
/> />
<ColorInput <ColorInput
v-model="btnDisabledTopBarTextColorLocal" v-model="btnDisabledTopBarTextColorLocal"
name="btnDisabledTopBarTextColor" name="btnDisabledTopBarTextColor"
:fallback="previewTheme.colors.btnDisabledTopBarText" :fallback="previewTheme.colors?.btnDisabledTopBarText"
:label="$t('settings.style.advanced_colors.top_bar')" :label="$t('settings.style.advanced_colors.top_bar')"
/> />
<h5>{{ $t('settings.style.advanced_colors.toggled') }}</h5> <h5>{{ $t('settings.style.advanced_colors.toggled') }}</h5>
<ColorInput <ColorInput
v-model="btnToggledColorLocal" v-model="btnToggledColorLocal"
name="btnToggledColor" name="btnToggledColor"
:fallback="previewTheme.colors.btnToggled" :fallback="previewTheme.colors?.btnToggled"
:label="$t('settings.background')" :label="$t('settings.background')"
/> />
<ColorInput <ColorInput
v-model="btnToggledTextColorLocal" v-model="btnToggledTextColorLocal"
name="btnToggledTextColor" name="btnToggledTextColor"
:fallback="previewTheme.colors.btnToggledText" :fallback="previewTheme.colors?.btnToggledText"
:label="$t('settings.text')" :label="$t('settings.text')"
/> />
<ContrastRatio :contrast="previewContrast.btnToggledText" /> <ContrastRatio :contrast="previewContrast.btnToggledText" />
<ColorInput <ColorInput
v-model="btnToggledPanelTextColorLocal" v-model="btnToggledPanelTextColorLocal"
name="btnToggledPanelTextColor" name="btnToggledPanelTextColor"
:fallback="previewTheme.colors.btnToggledPanelText" :fallback="previewTheme.colors?.btnToggledPanelText"
:label="$t('settings.style.advanced_colors.panel_header')" :label="$t('settings.style.advanced_colors.panel_header')"
/> />
<ContrastRatio :contrast="previewContrast.btnToggledPanelText" /> <ContrastRatio :contrast="previewContrast.btnToggledPanelText" />
<ColorInput <ColorInput
v-model="btnToggledTopBarTextColorLocal" v-model="btnToggledTopBarTextColorLocal"
name="btnToggledTopBarTextColor" name="btnToggledTopBarTextColor"
:fallback="previewTheme.colors.btnToggledTopBarText" :fallback="previewTheme.colors?.btnToggledTopBarText"
:label="$t('settings.style.advanced_colors.top_bar')" :label="$t('settings.style.advanced_colors.top_bar')"
/> />
<ContrastRatio :contrast="previewContrast.btnToggledTopBarText" /> <ContrastRatio :contrast="previewContrast.btnToggledTopBarText" />
@ -543,20 +558,20 @@
<ColorInput <ColorInput
v-model="tabColorLocal" v-model="tabColorLocal"
name="tabColor" name="tabColor"
:fallback="previewTheme.colors.tab" :fallback="previewTheme.colors?.tab"
:label="$t('settings.background')" :label="$t('settings.background')"
/> />
<ColorInput <ColorInput
v-model="tabTextColorLocal" v-model="tabTextColorLocal"
name="tabTextColor" name="tabTextColor"
:fallback="previewTheme.colors.tabText" :fallback="previewTheme.colors?.tabText"
:label="$t('settings.text')" :label="$t('settings.text')"
/> />
<ContrastRatio :contrast="previewContrast.tabText" /> <ContrastRatio :contrast="previewContrast.tabText" />
<ColorInput <ColorInput
v-model="tabActiveTextColorLocal" v-model="tabActiveTextColorLocal"
name="tabActiveTextColor" name="tabActiveTextColor"
:fallback="previewTheme.colors.tabActiveText" :fallback="previewTheme.colors?.tabActiveText"
:label="$t('settings.text')" :label="$t('settings.text')"
/> />
<ContrastRatio :contrast="previewContrast.tabActiveText" /> <ContrastRatio :contrast="previewContrast.tabActiveText" />
@ -566,13 +581,13 @@
<ColorInput <ColorInput
v-model="borderColorLocal" v-model="borderColorLocal"
name="borderColor" name="borderColor"
:fallback="previewTheme.colors.border" :fallback="previewTheme.colors?.border"
:label="$t('settings.style.common.color')" :label="$t('settings.style.common.color')"
/> />
<OpacityInput <OpacityInput
v-model="borderOpacityLocal" v-model="borderOpacityLocal"
name="borderOpacity" name="borderOpacity"
:fallback="previewTheme.opacity.border" :fallback="previewTheme.opacity?.border"
:disabled="borderColorLocal === 'transparent'" :disabled="borderColorLocal === 'transparent'"
/> />
</div> </div>
@ -581,25 +596,25 @@
<ColorInput <ColorInput
v-model="faintColorLocal" v-model="faintColorLocal"
name="faintColor" name="faintColor"
:fallback="previewTheme.colors.faint" :fallback="previewTheme.colors?.faint"
:label="$t('settings.text')" :label="$t('settings.text')"
/> />
<ColorInput <ColorInput
v-model="faintLinkColorLocal" v-model="faintLinkColorLocal"
name="faintLinkColor" name="faintLinkColor"
:fallback="previewTheme.colors.faintLink" :fallback="previewTheme.colors?.faintLink"
:label="$t('settings.links')" :label="$t('settings.links')"
/> />
<ColorInput <ColorInput
v-model="panelFaintColorLocal" v-model="panelFaintColorLocal"
name="panelFaintColor" name="panelFaintColor"
:fallback="previewTheme.colors.panelFaint" :fallback="previewTheme.colors?.panelFaint"
:label="$t('settings.style.advanced_colors.panel_header')" :label="$t('settings.style.advanced_colors.panel_header')"
/> />
<OpacityInput <OpacityInput
v-model="faintOpacityLocal" v-model="faintOpacityLocal"
name="faintOpacity" name="faintOpacity"
:fallback="previewTheme.opacity.faint" :fallback="previewTheme.opacity?.faint"
/> />
</div> </div>
<div class="color-item"> <div class="color-item">
@ -608,12 +623,12 @@
v-model="underlayColorLocal" v-model="underlayColorLocal"
name="underlay" name="underlay"
:label="$t('settings.style.advanced_colors.underlay')" :label="$t('settings.style.advanced_colors.underlay')"
:fallback="previewTheme.colors.underlay" :fallback="previewTheme.colors?.underlay"
/> />
<OpacityInput <OpacityInput
v-model="underlayOpacityLocal" v-model="underlayOpacityLocal"
name="underlayOpacity" name="underlayOpacity"
:fallback="previewTheme.opacity.underlay" :fallback="previewTheme.opacity?.underlay"
:disabled="underlayOpacityLocal === 'transparent'" :disabled="underlayOpacityLocal === 'transparent'"
/> />
</div> </div>
@ -623,7 +638,7 @@
v-model="wallpaperColorLocal" v-model="wallpaperColorLocal"
name="wallpaper" name="wallpaper"
:label="$t('settings.style.advanced_colors.wallpaper')" :label="$t('settings.style.advanced_colors.wallpaper')"
:fallback="previewTheme.colors.wallpaper" :fallback="previewTheme.colors?.wallpaper"
/> />
</div> </div>
<div class="color-item"> <div class="color-item">
@ -632,13 +647,13 @@
v-model="pollColorLocal" v-model="pollColorLocal"
name="poll" name="poll"
:label="$t('settings.background')" :label="$t('settings.background')"
:fallback="previewTheme.colors.poll" :fallback="previewTheme.colors?.poll"
/> />
<ColorInput <ColorInput
v-model="pollTextColorLocal" v-model="pollTextColorLocal"
name="pollText" name="pollText"
:label="$t('settings.text')" :label="$t('settings.text')"
:fallback="previewTheme.colors.pollText" :fallback="previewTheme.colors?.pollText"
/> />
</div> </div>
<div class="color-item"> <div class="color-item">
@ -647,7 +662,7 @@
v-model="iconColorLocal" v-model="iconColorLocal"
name="icon" name="icon"
:label="$t('settings.style.advanced_colors.icons')" :label="$t('settings.style.advanced_colors.icons')"
:fallback="previewTheme.colors.icon" :fallback="previewTheme.colors?.icon"
/> />
</div> </div>
<div class="color-item"> <div class="color-item">
@ -656,20 +671,20 @@
v-model="highlightColorLocal" v-model="highlightColorLocal"
name="highlight" name="highlight"
:label="$t('settings.background')" :label="$t('settings.background')"
:fallback="previewTheme.colors.highlight" :fallback="previewTheme.colors?.highlight"
/> />
<ColorInput <ColorInput
v-model="highlightTextColorLocal" v-model="highlightTextColorLocal"
name="highlightText" name="highlightText"
:label="$t('settings.text')" :label="$t('settings.text')"
:fallback="previewTheme.colors.highlightText" :fallback="previewTheme.colors?.highlightText"
/> />
<ContrastRatio :contrast="previewContrast.highlightText" /> <ContrastRatio :contrast="previewContrast.highlightText" />
<ColorInput <ColorInput
v-model="highlightLinkColorLocal" v-model="highlightLinkColorLocal"
name="highlightLink" name="highlightLink"
:label="$t('settings.links')" :label="$t('settings.links')"
:fallback="previewTheme.colors.highlightLink" :fallback="previewTheme.colors?.highlightLink"
/> />
<ContrastRatio :contrast="previewContrast.highlightLink" /> <ContrastRatio :contrast="previewContrast.highlightLink" />
</div> </div>
@ -679,26 +694,26 @@
v-model="popoverColorLocal" v-model="popoverColorLocal"
name="popover" name="popover"
:label="$t('settings.background')" :label="$t('settings.background')"
:fallback="previewTheme.colors.popover" :fallback="previewTheme.colors?.popover"
/> />
<OpacityInput <OpacityInput
v-model="popoverOpacityLocal" v-model="popoverOpacityLocal"
name="popoverOpacity" name="popoverOpacity"
:fallback="previewTheme.opacity.popover" :fallback="previewTheme.opacity?.popover"
:disabled="popoverOpacityLocal === 'transparent'" :disabled="popoverOpacityLocal === 'transparent'"
/> />
<ColorInput <ColorInput
v-model="popoverTextColorLocal" v-model="popoverTextColorLocal"
name="popoverText" name="popoverText"
:label="$t('settings.text')" :label="$t('settings.text')"
:fallback="previewTheme.colors.popoverText" :fallback="previewTheme.colors?.popoverText"
/> />
<ContrastRatio :contrast="previewContrast.popoverText" /> <ContrastRatio :contrast="previewContrast.popoverText" />
<ColorInput <ColorInput
v-model="popoverLinkColorLocal" v-model="popoverLinkColorLocal"
name="popoverLink" name="popoverLink"
:label="$t('settings.links')" :label="$t('settings.links')"
:fallback="previewTheme.colors.popoverLink" :fallback="previewTheme.colors?.popoverLink"
/> />
<ContrastRatio :contrast="previewContrast.popoverLink" /> <ContrastRatio :contrast="previewContrast.popoverLink" />
</div> </div>
@ -708,20 +723,20 @@
v-model="selectedPostColorLocal" v-model="selectedPostColorLocal"
name="selectedPost" name="selectedPost"
:label="$t('settings.background')" :label="$t('settings.background')"
:fallback="previewTheme.colors.selectedPost" :fallback="previewTheme.colors?.selectedPost"
/> />
<ColorInput <ColorInput
v-model="selectedPostTextColorLocal" v-model="selectedPostTextColorLocal"
name="selectedPostText" name="selectedPostText"
:label="$t('settings.text')" :label="$t('settings.text')"
:fallback="previewTheme.colors.selectedPostText" :fallback="previewTheme.colors?.selectedPostText"
/> />
<ContrastRatio :contrast="previewContrast.selectedPostText" /> <ContrastRatio :contrast="previewContrast.selectedPostText" />
<ColorInput <ColorInput
v-model="selectedPostLinkColorLocal" v-model="selectedPostLinkColorLocal"
name="selectedPostLink" name="selectedPostLink"
:label="$t('settings.links')" :label="$t('settings.links')"
:fallback="previewTheme.colors.selectedPostLink" :fallback="previewTheme.colors?.selectedPostLink"
/> />
<ContrastRatio :contrast="previewContrast.selectedPostLink" /> <ContrastRatio :contrast="previewContrast.selectedPostLink" />
</div> </div>
@ -731,20 +746,20 @@
v-model="selectedMenuColorLocal" v-model="selectedMenuColorLocal"
name="selectedMenu" name="selectedMenu"
:label="$t('settings.background')" :label="$t('settings.background')"
:fallback="previewTheme.colors.selectedMenu" :fallback="previewTheme.colors?.selectedMenu"
/> />
<ColorInput <ColorInput
v-model="selectedMenuTextColorLocal" v-model="selectedMenuTextColorLocal"
name="selectedMenuText" name="selectedMenuText"
:label="$t('settings.text')" :label="$t('settings.text')"
:fallback="previewTheme.colors.selectedMenuText" :fallback="previewTheme.colors?.selectedMenuText"
/> />
<ContrastRatio :contrast="previewContrast.selectedMenuText" /> <ContrastRatio :contrast="previewContrast.selectedMenuText" />
<ColorInput <ColorInput
v-model="selectedMenuLinkColorLocal" v-model="selectedMenuLinkColorLocal"
name="selectedMenuLink" name="selectedMenuLink"
:label="$t('settings.links')" :label="$t('settings.links')"
:fallback="previewTheme.colors.selectedMenuLink" :fallback="previewTheme.colors?.selectedMenuLink"
/> />
<ContrastRatio :contrast="previewContrast.selectedMenuLink" /> <ContrastRatio :contrast="previewContrast.selectedMenuLink" />
</div> </div>
@ -753,57 +768,57 @@
<ColorInput <ColorInput
v-model="chatBgColorLocal" v-model="chatBgColorLocal"
name="chatBgColor" name="chatBgColor"
:fallback="previewTheme.colors.bg" :fallback="previewTheme.colors?.bg"
:label="$t('settings.background')" :label="$t('settings.background')"
/> />
<h5>{{ $t('settings.style.advanced_colors.chat.incoming') }}</h5> <h5>{{ $t('settings.style.advanced_colors.chat.incoming') }}</h5>
<ColorInput <ColorInput
v-model="chatMessageIncomingBgColorLocal" v-model="chatMessageIncomingBgColorLocal"
name="chatMessageIncomingBgColor" name="chatMessageIncomingBgColor"
:fallback="previewTheme.colors.bg" :fallback="previewTheme.colors?.bg"
:label="$t('settings.background')" :label="$t('settings.background')"
/> />
<ColorInput <ColorInput
v-model="chatMessageIncomingTextColorLocal" v-model="chatMessageIncomingTextColorLocal"
name="chatMessageIncomingTextColor" name="chatMessageIncomingTextColor"
:fallback="previewTheme.colors.text" :fallback="previewTheme.colors?.text"
:label="$t('settings.text')" :label="$t('settings.text')"
/> />
<ColorInput <ColorInput
v-model="chatMessageIncomingLinkColorLocal" v-model="chatMessageIncomingLinkColorLocal"
name="chatMessageIncomingLinkColor" name="chatMessageIncomingLinkColor"
:fallback="previewTheme.colors.link" :fallback="previewTheme.colors?.link"
:label="$t('settings.links')" :label="$t('settings.links')"
/> />
<ColorInput <ColorInput
v-model="chatMessageIncomingBorderColorLocal" v-model="chatMessageIncomingBorderColorLocal"
name="chatMessageIncomingBorderLinkColor" name="chatMessageIncomingBorderLinkColor"
:fallback="previewTheme.colors.fg" :fallback="previewTheme.colors?.fg"
:label="$t('settings.style.advanced_colors.chat.border')" :label="$t('settings.style.advanced_colors.chat.border')"
/> />
<h5>{{ $t('settings.style.advanced_colors.chat.outgoing') }}</h5> <h5>{{ $t('settings.style.advanced_colors.chat.outgoing') }}</h5>
<ColorInput <ColorInput
v-model="chatMessageOutgoingBgColorLocal" v-model="chatMessageOutgoingBgColorLocal"
name="chatMessageOutgoingBgColor" name="chatMessageOutgoingBgColor"
:fallback="previewTheme.colors.bg" :fallback="previewTheme.colors?.bg"
:label="$t('settings.background')" :label="$t('settings.background')"
/> />
<ColorInput <ColorInput
v-model="chatMessageOutgoingTextColorLocal" v-model="chatMessageOutgoingTextColorLocal"
name="chatMessageOutgoingTextColor" name="chatMessageOutgoingTextColor"
:fallback="previewTheme.colors.text" :fallback="previewTheme.colors?.text"
:label="$t('settings.text')" :label="$t('settings.text')"
/> />
<ColorInput <ColorInput
v-model="chatMessageOutgoingLinkColorLocal" v-model="chatMessageOutgoingLinkColorLocal"
name="chatMessageOutgoingLinkColor" name="chatMessageOutgoingLinkColor"
:fallback="previewTheme.colors.link" :fallback="previewTheme.colors?.link"
:label="$t('settings.links')" :label="$t('settings.links')"
/> />
<ColorInput <ColorInput
v-model="chatMessageOutgoingBorderColorLocal" v-model="chatMessageOutgoingBorderColorLocal"
name="chatMessageOutgoingBorderLinkColor" name="chatMessageOutgoingBorderLinkColor"
:fallback="previewTheme.colors.bg" :fallback="previewTheme.colors?.bg"
:label="$t('settings.style.advanced_colors.chat.border')" :label="$t('settings.style.advanced_colors.chat.border')"
/> />
</div> </div>
@ -826,7 +841,7 @@
v-model="btnRadiusLocal" v-model="btnRadiusLocal"
name="btnRadius" name="btnRadius"
:label="$t('settings.btnRadius')" :label="$t('settings.btnRadius')"
:fallback="previewTheme.radii.btn" :fallback="previewTheme.radii?.btn"
max="16" max="16"
hard-min="0" hard-min="0"
/> />
@ -834,7 +849,7 @@
v-model="inputRadiusLocal" v-model="inputRadiusLocal"
name="inputRadius" name="inputRadius"
:label="$t('settings.inputRadius')" :label="$t('settings.inputRadius')"
:fallback="previewTheme.radii.input" :fallback="previewTheme.radii?.input"
max="9" max="9"
hard-min="0" hard-min="0"
/> />
@ -842,7 +857,7 @@
v-model="checkboxRadiusLocal" v-model="checkboxRadiusLocal"
name="checkboxRadius" name="checkboxRadius"
:label="$t('settings.checkboxRadius')" :label="$t('settings.checkboxRadius')"
:fallback="previewTheme.radii.checkbox" :fallback="previewTheme.radii?.checkbox"
max="16" max="16"
hard-min="0" hard-min="0"
/> />
@ -850,7 +865,7 @@
v-model="panelRadiusLocal" v-model="panelRadiusLocal"
name="panelRadius" name="panelRadius"
:label="$t('settings.panelRadius')" :label="$t('settings.panelRadius')"
:fallback="previewTheme.radii.panel" :fallback="previewTheme.radii?.panel"
max="50" max="50"
hard-min="0" hard-min="0"
/> />
@ -858,7 +873,7 @@
v-model="avatarRadiusLocal" v-model="avatarRadiusLocal"
name="avatarRadius" name="avatarRadius"
:label="$t('settings.avatarRadius')" :label="$t('settings.avatarRadius')"
:fallback="previewTheme.radii.avatar" :fallback="previewTheme.radii?.avatar"
max="28" max="28"
hard-min="0" hard-min="0"
/> />
@ -866,7 +881,7 @@
v-model="avatarAltRadiusLocal" v-model="avatarAltRadiusLocal"
name="avatarAltRadius" name="avatarAltRadius"
:label="$t('settings.avatarAltRadius')" :label="$t('settings.avatarAltRadius')"
:fallback="previewTheme.radii.avatarAlt" :fallback="previewTheme.radii?.avatarAlt"
max="28" max="28"
hard-min="0" hard-min="0"
/> />
@ -874,7 +889,7 @@
v-model="attachmentRadiusLocal" v-model="attachmentRadiusLocal"
name="attachmentRadius" name="attachmentRadius"
:label="$t('settings.attachmentRadius')" :label="$t('settings.attachmentRadius')"
:fallback="previewTheme.radii.attachment" :fallback="previewTheme.radii?.attachment"
max="50" max="50"
hard-min="0" hard-min="0"
/> />
@ -882,7 +897,7 @@
v-model="tooltipRadiusLocal" v-model="tooltipRadiusLocal"
name="tooltipRadius" name="tooltipRadius"
:label="$t('settings.tooltipRadius')" :label="$t('settings.tooltipRadius')"
:fallback="previewTheme.radii.tooltip" :fallback="previewTheme.radii?.tooltip"
max="50" max="50"
hard-min="0" hard-min="0"
/> />
@ -890,7 +905,7 @@
v-model="chatMessageRadiusLocal" v-model="chatMessageRadiusLocal"
name="chatMessageRadius" name="chatMessageRadius"
:label="$t('settings.chatMessageRadius')" :label="$t('settings.chatMessageRadius')"
:fallback="previewTheme.radii.chatMessage || 2" :fallback="previewTheme.radii?.chatMessage || 2"
max="50" max="50"
hard-min="0" hard-min="0"
/> />
@ -996,26 +1011,26 @@
v-model="fontsLocal.interface" v-model="fontsLocal.interface"
name="ui" name="ui"
:label="$t('settings.style.fonts.components.interface')" :label="$t('settings.style.fonts.components.interface')"
:fallback="previewTheme.fonts.interface" :fallback="previewTheme.fonts?.interface"
no-inherit="1" no-inherit="1"
/> />
<FontControl <FontControl
v-model="fontsLocal.input" v-model="fontsLocal.input"
name="input" name="input"
:label="$t('settings.style.fonts.components.input')" :label="$t('settings.style.fonts.components.input')"
:fallback="previewTheme.fonts.input" :fallback="previewTheme.fonts?.input"
/> />
<FontControl <FontControl
v-model="fontsLocal.post" v-model="fontsLocal.post"
name="post" name="post"
:label="$t('settings.style.fonts.components.post')" :label="$t('settings.style.fonts.components.post')"
:fallback="previewTheme.fonts.post" :fallback="previewTheme.fonts?.post"
/> />
<FontControl <FontControl
v-model="fontsLocal.postCode" v-model="fontsLocal.postCode"
name="postCode" name="postCode"
:label="$t('settings.style.fonts.components.postCode')" :label="$t('settings.style.fonts.components.postCode')"
:fallback="previewTheme.fonts.postCode" :fallback="previewTheme.fonts?.postCode"
/> />
</div> </div>
</tab-switcher> </tab-switcher>

View file

@ -745,6 +745,8 @@
"enable_web_push_always_show_tip": "Some browsers (Chromium, Chrome) require that push messages always result in a notification, otherwise generic 'Website was updated in background' is shown, enable this to prevent this notification from showing, as Chrome seem to hide push notifications if tab is in focus. Can result in showing duplicate notifications on other browsers.", "enable_web_push_always_show_tip": "Some browsers (Chromium, Chrome) require that push messages always result in a notification, otherwise generic 'Website was updated in background' is shown, enable this to prevent this notification from showing, as Chrome seem to hide push notifications if tab is in focus. Can result in showing duplicate notifications on other browsers.",
"more_settings": "More settings", "more_settings": "More settings",
"style": { "style": {
"themes2_outdated": "Editor for Themes V2 is no longer supported and presented here for sake of legacy.",
"update_preview": "Update preview",
"themes3": { "themes3": {
"define": "Override", "define": "Override",
"font": { "font": {

View file

@ -1,10 +1,21 @@
import Cookies from 'js-cookie' import Cookies from 'js-cookie'
import { setPreset, applyTheme, applyConfig } from '../services/style_setter/style_setter.js' import { applyConfig } from '../services/style_setter/style_setter.js'
import messages from '../i18n/messages' import messages from '../i18n/messages'
import { set } from 'lodash' import { set } from 'lodash'
import localeService from '../services/locale/locale.service.js' import localeService from '../services/locale/locale.service.js'
const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage' const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage'
const APPEARANCE_SETTINGS_KEYS = new Set([
'sidebarColumnWidth',
'contentColumnWidth',
'notifsColumnWidth',
'textSize',
'navbarSize',
'panelHeaderSize',
'forcedRoundness',
'emojiSize',
'emojiReactionsScale'
])
const browserLocale = (window.navigator.language || 'en').split('-')[0] const browserLocale = (window.navigator.language || 'en').split('-')[0]
@ -81,6 +92,11 @@ export const defaultState = {
chatMention: true, chatMention: true,
polls: true polls: true
}, },
palette: null,
theme3hacks: {
underlay: 'none',
badgeColor: null
},
webPushNotifications: false, webPushNotifications: false,
webPushAlwaysShowNotifications: false, webPushAlwaysShowNotifications: false,
muteWords: [], muteWords: [],
@ -185,7 +201,6 @@ const config = {
applyConfig(state) applyConfig(state)
}, },
setOption (state, { name, value }) { setOption (state, { name, value }) {
console.log('SET OPTION', state, name, value)
set(state, name, value) set(state, name, value)
}, },
setHighlight (state, { user, color, type }) { setHighlight (state, { user, color, type }) {
@ -261,30 +276,23 @@ const config = {
} }
} else { } else {
commit('setOption', { name, value }) commit('setOption', { name, value })
if (
name.startsWith('theme3hacks') ||
APPEARANCE_SETTINGS_KEYS.has(name)
) {
applyConfig(state)
}
switch (name) { switch (name) {
case 'theme': case 'theme':
setPreset(value) dispatch('setTheme', { themeName: value, recompile: true })
break
case 'sidebarColumnWidth':
case 'contentColumnWidth':
case 'notifsColumnWidth':
case 'textSize':
case 'navbarSize':
case 'panelHeaderSize':
case 'forcedRoundness':
case 'emojiSize':
case 'emojiReactionsScale':
applyConfig(state)
break break
case 'customTheme': case 'customTheme':
case 'customThemeSource': { case 'customThemeSource': {
const { themeDebug } = state if (!value.ignore) dispatch('setTheme', { themeData: value })
applyTheme(value, () => {}, themeDebug)
break break
} }
case 'themeDebug': { case 'themeDebug': {
const { customTheme, customThemeSource } = state dispatch('setTheme', { recompile: true })
applyTheme(customTheme || customThemeSource, () => {}, value)
break break
} }
case 'interfaceLanguage': case 'interfaceLanguage':

View file

@ -1,5 +1,6 @@
import { getPreset, applyTheme } from '../services/style_setter/style_setter.js' import { getPreset, applyTheme, tryLoadCache } from '../services/style_setter/style_setter.js'
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' import { CURRENT_VERSION, generatePreset } from 'src/services/theme_data/theme_data.service.js'
import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
import apiService from '../services/api/api.service.js' import apiService from '../services/api/api.service.js'
import { instanceDefaultProperties } from './config.js' import { instanceDefaultProperties } from './config.js'
import { langCodeToCldrName, ensureFinalFallback } from '../i18n/languages.js' import { langCodeToCldrName, ensureFinalFallback } from '../i18n/languages.js'
@ -286,9 +287,6 @@ const instance = {
dispatch('initializeSocket') dispatch('initializeSocket')
} }
break break
case 'theme':
dispatch('setTheme', value)
break
} }
}, },
async getStaticEmoji ({ commit }) { async getStaticEmoji ({ commit }) {
@ -378,25 +376,86 @@ const instance = {
} }
}, },
setTheme ({ commit, rootState }, themeName) { setTheme ({ commit, state, rootState }, { themeName, themeData, recompile } = {}) {
commit('setInstanceOption', { name: 'theme', value: themeName }) // const {
getPreset(themeName) // themeApplied
.then(themeData => { // } = rootState.interface
commit('setInstanceOption', { name: 'themeData', value: themeData }) const {
// No need to apply theme if there's user theme already theme: instanceThemeName
const { customTheme, themeDebug } = rootState.config } = state
const { themeApplied } = rootState.interface
if (customTheme || themeApplied) return
// New theme presets don't have 'theme' property, they use 'source' const {
const themeSource = themeData.source customTheme: userThemeSnapshot,
if (!themeData.theme || (themeSource && themeSource.themeEngineVersion === CURRENT_VERSION)) { customThemeSource: userThemeSource,
applyTheme(themeSource, null, themeDebug) forceThemeRecompilation,
themeDebug
} = rootState.config
const forceRecompile = forceThemeRecompilation || recompile
// If we're not not forced to recompile try using
// cache (tryLoadCache return true if load successful)
if (!forceRecompile && !themeDebug && tryLoadCache()) {
commit('setThemeApplied')
}
const normalizeThemeData = (themeData) => {
console.log('NORMAL', themeData)
if (themeData.themeFileVerison === 1) {
return generatePreset(themeData).theme
}
// New theme presets don't have 'theme' property, they use 'source'
const themeSource = themeData.source
let out // shout, shout let it all out
if (!themeData.theme || (themeSource && themeSource.themeEngineVersion === CURRENT_VERSION)) {
out = themeSource || themeData
} else {
out = themeData.theme
}
// generatePreset here basically creates/updates "snapshot",
// while also fixing the 2.2 -> 2.3 colors/shadows/etc
return generatePreset(out).theme
}
let promise = null
if (themeName) {
// commit('setInstanceOption', { name: 'theme', value: themeName })
promise = getPreset(themeName)
.then(themeData => {
// commit('setInstanceOption', { name: 'themeData', value: themeData })
return normalizeThemeData(themeData)
})
} else if (themeData) {
promise = Promise.resolve(normalizeThemeData(themeData))
} else {
if (userThemeSource || userThemeSnapshot) {
if (userThemeSource && userThemeSource.themeEngineVersion === CURRENT_VERSION) {
promise = Promise.resolve(normalizeThemeData(userThemeSource))
} else { } else {
applyTheme(themeData.theme, null, themeDebug) promise = Promise.resolve(normalizeThemeData(userThemeSnapshot))
} }
commit('setThemeApplied') } else if (instanceThemeName) {
promise = getPreset(themeName).then(themeData => normalizeThemeData(themeData))
}
}
promise
.then(realThemeData => {
console.log('FR FR 1', realThemeData)
const ruleset = convertTheme2To3(realThemeData)
console.log('FR FR 2', ruleset)
applyTheme(
ruleset,
() => commit('setThemeApplied'),
themeDebug
)
}) })
return promise
}, },
fetchEmoji ({ dispatch, state }) { fetchEmoji ({ dispatch, state }) {
if (!state.customEmojiFetched) { if (!state.customEmojiFetched) {

View file

@ -1,7 +1,5 @@
import { hex2rgb } from '../color_convert/color_convert.js' import { hex2rgb } from '../color_convert/color_convert.js'
import { generatePreset } from '../theme_data/theme_data.service.js'
import { init, getEngineChecksum } from '../theme_data/theme_data_3.service.js' import { init, getEngineChecksum } from '../theme_data/theme_data_3.service.js'
import { convertTheme2To3 } from '../theme_data/theme2_to_theme3.js'
import { getCssRules } from '../theme_data/css_utils.js' import { getCssRules } from '../theme_data/css_utils.js'
import { defaultState } from '../../modules/config.js' import { defaultState } from '../../modules/config.js'
import { chunk } from 'lodash' import { chunk } from 'lodash'
@ -45,25 +43,19 @@ const adoptStyleSheets = (styles) => {
// is nothing to do here. // is nothing to do here.
} }
export const generateTheme = async (input, callbacks, debug) => { export const generateTheme = async (inputRuleset, callbacks, debug) => {
const { const {
onNewRule = (rule, isLazy) => {}, onNewRule = (rule, isLazy) => {},
onLazyFinished = () => {}, onLazyFinished = () => {},
onEagerFinished = () => {} onEagerFinished = () => {}
} = callbacks } = callbacks
let extraRules
if (input.themeFileVersion === 1) {
extraRules = convertTheme2To3(input)
} else {
const { theme } = generatePreset(input)
extraRules = convertTheme2To3(theme)
}
// Assuming that "worst case scenario background" is panel background since it's the most likely one // Assuming that "worst case scenario background" is panel background since it's the most likely one
const themes3 = init(extraRules, extraRules[0].directives['--bg'].split('|')[1].trim(), debug) const themes3 = init({
inputRuleset,
console.log('DEBUG 2 IS', debug) ultimateBackgroundColor: inputRuleset[0].directives['--bg'].split('|')[1].trim(),
debug
})
getCssRules(themes3.eager, debug).forEach(rule => { getCssRules(themes3.eager, debug).forEach(rule => {
// Hacks to support multiple selectors on same component // Hacks to support multiple selectors on same component
@ -158,8 +150,6 @@ export const applyTheme = async (input, onFinish = (data) => {}, debug) => {
const eagerStyles = createStyleSheet(EAGER_STYLE_ID) const eagerStyles = createStyleSheet(EAGER_STYLE_ID)
const lazyStyles = createStyleSheet(LAZY_STYLE_ID) const lazyStyles = createStyleSheet(LAZY_STYLE_ID)
console.log('DEBUG IS', debug)
const { lazyProcessFunc } = await generateTheme( const { lazyProcessFunc } = await generateTheme(
input, input,
{ {
@ -212,7 +202,6 @@ const extractStyleConfig = ({
textSize textSize
} }
console.log(forcedRoundness)
switch (forcedRoundness) { switch (forcedRoundness) {
case 'disable': case 'disable':
break break
@ -321,5 +310,3 @@ export const getPreset = (val) => {
return { theme: data, source: theme.source } return { theme: data, source: theme.source }
}) })
} }
export const setPreset = (val) => getPreset(val).then(data => applyTheme(data))

View file

@ -265,6 +265,7 @@ export const convertTheme2To3 = (data) => {
const newRules = [] const newRules = []
Object.keys(data.fonts || {}).forEach(key => { Object.keys(data.fonts || {}).forEach(key => {
if (!fontsKeys.has(key)) return if (!fontsKeys.has(key)) return
if (!data.fonts[key]) return
const originalFont = data.fonts[key].family const originalFont = data.fonts[key].family
const rule = { source: '2to3' } const rule = { source: '2to3' }

View file

@ -149,7 +149,14 @@ const ruleToSelector = genericRuleToSelector(components)
export const getEngineChecksum = () => engineChecksum export const getEngineChecksum = () => engineChecksum
export const init = (extraRuleset, ultimateBackgroundColor, debug) => { export const init = ({
inputRuleset,
ultimateBackgroundColor,
debug = false,
liteMode = false,
rootComponentName = 'Root'
}) => {
if (!inputRuleset) throw new Error('Ruleset is null or undefined!')
const staticVars = {} const staticVars = {}
const stacked = {} const stacked = {}
const computed = {} const computed = {}
@ -158,7 +165,7 @@ export const init = (extraRuleset, ultimateBackgroundColor, debug) => {
...Object.values(components) ...Object.values(components)
.map(c => (c.defaultRules || []).map(r => ({ component: c.name, ...r, source: 'Built-in' }))) .map(c => (c.defaultRules || []).map(r => ({ component: c.name, ...r, source: 'Built-in' })))
.reduce((acc, arr) => [...acc, ...arr], []), .reduce((acc, arr) => [...acc, ...arr], []),
...extraRuleset ...inputRuleset
].map(rule => { ].map(rule => {
normalizeCombination(rule) normalizeCombination(rule)
let currentParent = rule.parent let currentParent = rule.parent
@ -451,7 +458,7 @@ export const init = (extraRuleset, ultimateBackgroundColor, debug) => {
} }
const t0 = performance.now() const t0 = performance.now()
const combinations = processInnerComponent(components.Root) const combinations = processInnerComponent(components[rootComponentName] ?? components.Root)
const t1 = performance.now() const t1 = performance.now()
console.debug('Tree traveral took ' + (t1 - t0) + ' ms') console.debug('Tree traveral took ' + (t1 - t0) + ' ms')

View file

@ -66,7 +66,7 @@ describe('Theme Data 3', () => {
this.timeout(5000) this.timeout(5000)
it('Test initialization without anything', () => { it('Test initialization without anything', () => {
const out = init([], '#DEADAF') const out = init({ ruleset: [], ultimateBackgroundColor: '#DEADAF' })
expect(out).to.have.property('eager') expect(out).to.have.property('eager')
expect(out).to.have.property('lazy') expect(out).to.have.property('lazy')
@ -85,13 +85,16 @@ describe('Theme Data 3', () => {
}) })
it('Test initialization with a basic palette', () => { it('Test initialization with a basic palette', () => {
const out = init([{ const out = init({
component: 'Root', ruleset: [{
directives: { component: 'Root',
'--bg': 'color | #008080', directives: {
'--fg': 'color | #00C0A0' '--bg': 'color | #008080',
} '--fg': 'color | #00C0A0'
}], '#DEADAF') }
}],
ultimateBackgroundColor: '#DEADAF'
})
expect(out.staticVars).to.have.property('bg').equal('#008080') expect(out.staticVars).to.have.property('bg').equal('#008080')
expect(out.staticVars).to.have.property('fg').equal('#00C0A0') expect(out.staticVars).to.have.property('fg').equal('#00C0A0')
@ -105,17 +108,20 @@ describe('Theme Data 3', () => {
}) })
it('Test initialization with opacity', () => { it('Test initialization with opacity', () => {
const out = init([{ const out = init({
component: 'Root', ruleset: [{
directives: { component: 'Root',
'--bg': 'color | #008080' directives: {
} '--bg': 'color | #008080'
}, { }
component: 'Panel', }, {
directives: { component: 'Panel',
opacity: 0.5 directives: {
} opacity: 0.5
}], '#DEADAF') }
}],
ultimateBackgroundColor: '#DEADAF'
})
expect(out.staticVars).to.have.property('bg').equal('#008080') expect(out.staticVars).to.have.property('bg').equal('#008080')