aboutsummaryrefslogtreecommitdiff
path: root/src/components/style_switcher
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/style_switcher')
-rw-r--r--src/components/style_switcher/preview.vue188
-rw-r--r--src/components/style_switcher/style_switcher.js592
-rw-r--r--src/components/style_switcher/style_switcher.scss38
-rw-r--r--src/components/style_switcher/style_switcher.vue423
4 files changed, 902 insertions, 339 deletions
diff --git a/src/components/style_switcher/preview.vue b/src/components/style_switcher/preview.vue
index 101a32bd..9d984659 100644
--- a/src/components/style_switcher/preview.vue
+++ b/src/components/style_switcher/preview.vue
@@ -1,101 +1,117 @@
<template>
- <div class="panel dummy">
- <div class="panel-heading">
- <div class="title">
- {{ $t('settings.style.preview.header') }}
- <span class="badge badge-notification">
- 99
+ <div class="preview-container">
+ <div class="underlay underlay-preview" />
+ <div class="panel dummy">
+ <div class="panel-heading">
+ <div class="title">
+ {{ $t('settings.style.preview.header') }}
+ <span class="badge badge-notification">
+ 99
+ </span>
+ </div>
+ <span class="faint">
+ {{ $t('settings.style.preview.header_faint') }}
+ </span>
+ <span class="alert error">
+ {{ $t('settings.style.preview.error') }}
</span>
+ <button class="btn">
+ {{ $t('settings.style.preview.button') }}
+ </button>
</div>
- <span class="faint">
- {{ $t('settings.style.preview.header_faint') }}
- </span>
- <span class="alert error">
- {{ $t('settings.style.preview.error') }}
- </span>
- <button class="btn">
- {{ $t('settings.style.preview.button') }}
- </button>
- </div>
- <div class="panel-body theme-preview-content">
- <div class="post">
- <div class="avatar">
- ( ͡° ͜ʖ ͡°)
- </div>
- <div class="content">
- <h4>
- {{ $t('settings.style.preview.content') }}
- </h4>
+ <div class="panel-body theme-preview-content">
+ <div class="post">
+ <div class="avatar still-image">
+ ( ͡° ͜ʖ ͡°)
+ </div>
+ <div class="content">
+ <h4>
+ {{ $t('settings.style.preview.content') }}
+ </h4>
- <i18n path="settings.style.preview.text">
- <code style="font-family: var(--postCodeFont)">
- {{ $t('settings.style.preview.mono') }}
- </code>
- <a style="color: var(--link)">
- {{ $t('settings.style.preview.link') }}
- </a>
- </i18n>
+ <i18n path="settings.style.preview.text">
+ <code style="font-family: var(--postCodeFont)">
+ {{ $t('settings.style.preview.mono') }}
+ </code>
+ <a style="color: var(--link)">
+ {{ $t('settings.style.preview.link') }}
+ </a>
+ </i18n>
- <div class="icons">
- <i
- style="color: var(--cBlue)"
- class="button-icon icon-reply"
- />
- <i
- style="color: var(--cGreen)"
- class="button-icon icon-retweet"
- />
- <i
- style="color: var(--cOrange)"
- class="button-icon icon-star"
- />
- <i
- style="color: var(--cRed)"
- class="button-icon icon-cancel"
- />
+ <div class="icons">
+ <i
+ style="color: var(--cBlue)"
+ class="button-icon icon-reply"
+ />
+ <i
+ style="color: var(--cGreen)"
+ class="button-icon icon-retweet"
+ />
+ <i
+ style="color: var(--cOrange)"
+ class="button-icon icon-star"
+ />
+ <i
+ style="color: var(--cRed)"
+ class="button-icon icon-cancel"
+ />
+ </div>
</div>
</div>
- </div>
- <div class="after-post">
- <div class="avatar-alt">
- :^)
- </div>
- <div class="content">
- <i18n
- path="settings.style.preview.fine_print"
- tag="span"
- class="faint"
- >
- <a style="color: var(--faintLink)">
- {{ $t('settings.style.preview.faint_link') }}
- </a>
- </i18n>
+ <div class="after-post">
+ <div class="avatar-alt">
+ :^)
+ </div>
+ <div class="content">
+ <i18n
+ path="settings.style.preview.fine_print"
+ tag="span"
+ class="faint"
+ >
+ <a style="color: var(--faintLink)">
+ {{ $t('settings.style.preview.faint_link') }}
+ </a>
+ </i18n>
+ </div>
</div>
- </div>
- <div class="separator" />
+ <div class="separator" />
- <span class="alert error">
- {{ $t('settings.style.preview.error') }}
- </span>
- <input
- :value="$t('settings.style.preview.input')"
- type="text"
- >
-
- <div class="actions">
- <span class="checkbox">
- <input
- id="preview_checkbox"
- checked="very yes"
- type="checkbox"
- >
- <label for="preview_checkbox">{{ $t('settings.style.preview.checkbox') }}</label>
+ <span class="alert error">
+ {{ $t('settings.style.preview.error') }}
</span>
- <button class="btn">
- {{ $t('settings.style.preview.button') }}
- </button>
+ <input
+ :value="$t('settings.style.preview.input')"
+ type="text"
+ >
+
+ <div class="actions">
+ <span class="checkbox">
+ <input
+ id="preview_checkbox"
+ checked="very yes"
+ type="checkbox"
+ >
+ <label for="preview_checkbox">{{ $t('settings.style.preview.checkbox') }}</label>
+ </span>
+ <button class="btn">
+ {{ $t('settings.style.preview.button') }}
+ </button>
+ </div>
</div>
</div>
</div>
</template>
+
+<style lang="scss">
+.preview-container {
+ position: relative;
+}
+.underlay-preview {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 10px;
+ right: 10px;
+}
+</style>
diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index ebde4475..a7f586f4 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -1,6 +1,29 @@
-import { rgb2hex, hex2rgb, getContrastRatio, alphaBlend } from '../../services/color_convert/color_convert.js'
import { set, delete as del } from 'vue'
-import { generateColors, generateShadows, generateRadii, generateFonts, composePreset, getThemes } from '../../services/style_setter/style_setter.js'
+import {
+ rgb2hex,
+ hex2rgb,
+ getContrastRatioLayers
+} from '../../services/color_convert/color_convert.js'
+import {
+ DEFAULT_SHADOWS,
+ generateColors,
+ generateShadows,
+ generateRadii,
+ generateFonts,
+ composePreset,
+ getThemes,
+ shadows2to3,
+ colors2to3
+} from '../../services/style_setter/style_setter.js'
+import {
+ SLOT_INHERITANCE
+} from '../../services/theme_data/pleromafe.js'
+import {
+ CURRENT_VERSION,
+ OPACITIES,
+ getLayers,
+ getOpacitySlot
+} from '../../services/theme_data/theme_data.service.js'
import ColorInput from '../color_input/color_input.vue'
import RangeInput from '../range_input/range_input.vue'
import OpacityInput from '../opacity_input/opacity_input.vue'
@@ -24,11 +47,22 @@ const v1OnlyNames = [
'cOrange'
].map(_ => _ + 'ColorLocal')
+const colorConvert = (color) => {
+ if (color.startsWith('--') || color === 'transparent') {
+ return color
+ } else {
+ return hex2rgb(color)
+ }
+}
+
export default {
data () {
return {
availableStyles: [],
selected: this.$store.getters.mergedConfig.theme,
+ themeWarning: undefined,
+ tempImportFile: undefined,
+ engineVersion: 0,
previewShadows: {},
previewColors: {},
@@ -45,51 +79,13 @@ export default {
keepRoundness: false,
keepFonts: false,
- textColorLocal: '',
- linkColorLocal: '',
-
- bgColorLocal: '',
- bgOpacityLocal: undefined,
-
- fgColorLocal: '',
- fgTextColorLocal: undefined,
- fgLinkColorLocal: undefined,
-
- btnColorLocal: undefined,
- btnTextColorLocal: undefined,
- btnOpacityLocal: undefined,
-
- inputColorLocal: undefined,
- inputTextColorLocal: undefined,
- inputOpacityLocal: undefined,
+ ...Object.keys(SLOT_INHERITANCE)
+ .map(key => [key, ''])
+ .reduce((acc, [key, val]) => ({ ...acc, [ key + 'ColorLocal' ]: val }), {}),
- panelColorLocal: undefined,
- panelTextColorLocal: undefined,
- panelLinkColorLocal: undefined,
- panelFaintColorLocal: undefined,
- panelOpacityLocal: undefined,
-
- topBarColorLocal: undefined,
- topBarTextColorLocal: undefined,
- topBarLinkColorLocal: undefined,
-
- alertErrorColorLocal: undefined,
- alertWarningColorLocal: undefined,
-
- badgeOpacityLocal: undefined,
- badgeNotificationColorLocal: undefined,
-
- borderColorLocal: undefined,
- borderOpacityLocal: undefined,
-
- faintColorLocal: undefined,
- faintOpacityLocal: undefined,
- faintLinkColorLocal: undefined,
-
- cRedColorLocal: '',
- cBlueColorLocal: '',
- cGreenColorLocal: '',
- cOrangeColorLocal: '',
+ ...Object.keys(OPACITIES)
+ .map(key => [key, ''])
+ .reduce((acc, [key, val]) => ({ ...acc, [ key + 'OpacityLocal' ]: val }), {}),
shadowSelected: undefined,
shadowsLocal: {},
@@ -108,69 +104,105 @@ export default {
created () {
const self = this
- getThemes().then((themesComplete) => {
- self.availableStyles = themesComplete
- })
+ getThemes()
+ .then((promises) => {
+ return Promise.all(
+ Object.entries(promises)
+ .map(([k, v]) => v.then(res => [k, res]))
+ )
+ })
+ .then(themes => themes.reduce((acc, [k, v]) => {
+ if (v) {
+ return {
+ ...acc,
+ [k]: v
+ }
+ } else {
+ return acc
+ }
+ }, {}))
+ .then((themesComplete) => {
+ self.availableStyles = themesComplete
+ })
},
mounted () {
- this.normalizeLocalState(this.$store.getters.mergedConfig.customTheme)
+ this.loadThemeFromLocalStorage()
if (typeof this.shadowSelected === 'undefined') {
this.shadowSelected = this.shadowsAvailable[0]
}
},
computed: {
+ themeWarningHelp () {
+ if (!this.themeWarning) return
+ const t = this.$t
+ const pre = 'settings.style.switcher.help.'
+ const {
+ origin,
+ themeEngineVersion,
+ type,
+ noActionsPossible
+ } = this.themeWarning
+ if (origin === 'file') {
+ // Loaded v2 theme from file
+ if (themeEngineVersion === 2 && type === 'wrong_version') {
+ return t(pre + 'v2_imported')
+ }
+ if (themeEngineVersion > CURRENT_VERSION) {
+ return t(pre + 'future_version_imported') + ' ' +
+ (
+ noActionsPossible
+ ? t(pre + 'snapshot_missing')
+ : t(pre + 'snapshot_present')
+ )
+ }
+ if (themeEngineVersion < CURRENT_VERSION) {
+ return t(pre + 'future_version_imported') + ' ' +
+ (
+ noActionsPossible
+ ? t(pre + 'snapshot_missing')
+ : t(pre + 'snapshot_present')
+ )
+ }
+ } else if (origin === 'localStorage') {
+ if (type === 'snapshot_source_mismatch') {
+ return t(pre + 'snapshot_source_mismatch')
+ }
+ // FE upgraded from v2
+ if (themeEngineVersion === 2) {
+ return t(pre + 'upgraded_from_v2')
+ }
+ // Admin downgraded FE
+ if (themeEngineVersion > CURRENT_VERSION) {
+ return t(pre + 'fe_downgraded') + ' ' +
+ (
+ noActionsPossible
+ ? t(pre + 'migration_snapshot_ok')
+ : t(pre + 'migration_snapshot_gone')
+ )
+ }
+ // Admin upgraded FE
+ if (themeEngineVersion < CURRENT_VERSION) {
+ return t(pre + 'fe_upgraded') + ' ' +
+ (
+ noActionsPossible
+ ? t(pre + 'migration_snapshot_ok')
+ : t(pre + 'migration_snapshot_gone')
+ )
+ }
+ }
+ },
selectedVersion () {
return Array.isArray(this.selected) ? 1 : 2
},
currentColors () {
- return {
- bg: this.bgColorLocal,
- text: this.textColorLocal,
- link: this.linkColorLocal,
-
- fg: this.fgColorLocal,
- fgText: this.fgTextColorLocal,
- fgLink: this.fgLinkColorLocal,
-
- panel: this.panelColorLocal,
- panelText: this.panelTextColorLocal,
- panelLink: this.panelLinkColorLocal,
- panelFaint: this.panelFaintColorLocal,
-
- input: this.inputColorLocal,
- inputText: this.inputTextColorLocal,
-
- topBar: this.topBarColorLocal,
- topBarText: this.topBarTextColorLocal,
- topBarLink: this.topBarLinkColorLocal,
-
- btn: this.btnColorLocal,
- btnText: this.btnTextColorLocal,
-
- alertError: this.alertErrorColorLocal,
- alertWarning: this.alertWarningColorLocal,
- badgeNotification: this.badgeNotificationColorLocal,
-
- faint: this.faintColorLocal,
- faintLink: this.faintLinkColorLocal,
- border: this.borderColorLocal,
-
- cRed: this.cRedColorLocal,
- cBlue: this.cBlueColorLocal,
- cGreen: this.cGreenColorLocal,
- cOrange: this.cOrangeColorLocal
- }
+ return Object.keys(SLOT_INHERITANCE)
+ .map(key => [key, this[key + 'ColorLocal']])
+ .reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {})
},
currentOpacity () {
- return {
- bg: this.bgOpacityLocal,
- btn: this.btnOpacityLocal,
- input: this.inputOpacityLocal,
- panel: this.panelOpacityLocal,
- topBar: this.topBarOpacityLocal,
- border: this.borderOpacityLocal,
- faint: this.faintOpacityLocal
- }
+ return Object.keys(OPACITIES)
+ .map(key => [key, this[key + 'OpacityLocal']])
+ .reduce((acc, [key, val]) => ({ ...acc, [ key ]: val }), {})
},
currentRadii () {
return {
@@ -193,75 +225,66 @@ export default {
},
// This needs optimization maybe
previewContrast () {
- if (!this.previewTheme.colors.bg) return {}
- const colors = this.previewTheme.colors
- const opacity = this.previewTheme.opacity
- if (!colors.bg) return {}
- const hints = (ratio) => ({
- text: ratio.toPrecision(3) + ':1',
- // AA level, AAA level
- aa: ratio >= 4.5,
- aaa: ratio >= 7,
- // same but for 18pt+ texts
- laa: ratio >= 3,
- laaa: ratio >= 4.5
- })
-
- // fgsfds :DDDD
- const fgs = {
- text: hex2rgb(colors.text),
- panelText: hex2rgb(colors.panelText),
- panelLink: hex2rgb(colors.panelLink),
- btnText: hex2rgb(colors.btnText),
- topBarText: hex2rgb(colors.topBarText),
- inputText: hex2rgb(colors.inputText),
-
- link: hex2rgb(colors.link),
- topBarLink: hex2rgb(colors.topBarLink),
-
- red: hex2rgb(colors.cRed),
- green: hex2rgb(colors.cGreen),
- blue: hex2rgb(colors.cBlue),
- orange: hex2rgb(colors.cOrange)
- }
-
- const bgs = {
- bg: hex2rgb(colors.bg),
- btn: hex2rgb(colors.btn),
- panel: hex2rgb(colors.panel),
- topBar: hex2rgb(colors.topBar),
- input: hex2rgb(colors.input),
- alertError: hex2rgb(colors.alertError),
- alertWarning: hex2rgb(colors.alertWarning),
- badgeNotification: hex2rgb(colors.badgeNotification)
- }
-
- /* This is a bit confusing because "bottom layer" used is text color
- * This is done to get worst case scenario when background below transparent
- * layer matches text color, making it harder to read the lower alpha is.
- */
- const ratios = {
- bgText: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.text), fgs.text),
- bgLink: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.link), fgs.link),
- bgRed: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.red), fgs.red),
- bgGreen: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.green), fgs.green),
- bgBlue: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.blue), fgs.blue),
- bgOrange: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.orange), fgs.orange),
-
- tintText: getContrastRatio(alphaBlend(bgs.bg, 0.5, fgs.panelText), fgs.text),
-
- panelText: getContrastRatio(alphaBlend(bgs.panel, opacity.panel, fgs.panelText), fgs.panelText),
- panelLink: getContrastRatio(alphaBlend(bgs.panel, opacity.panel, fgs.panelLink), fgs.panelLink),
-
- btnText: getContrastRatio(alphaBlend(bgs.btn, opacity.btn, fgs.btnText), fgs.btnText),
-
- inputText: getContrastRatio(alphaBlend(bgs.input, opacity.input, fgs.inputText), fgs.inputText),
-
- topBarText: getContrastRatio(alphaBlend(bgs.topBar, opacity.topBar, fgs.topBarText), fgs.topBarText),
- topBarLink: getContrastRatio(alphaBlend(bgs.topBar, opacity.topBar, fgs.topBarLink), fgs.topBarLink)
+ try {
+ if (!this.previewTheme.colors.bg) return {}
+ const colors = this.previewTheme.colors
+ const opacity = this.previewTheme.opacity
+ if (!colors.bg) return {}
+ const hints = (ratio) => ({
+ text: ratio.toPrecision(3) + ':1',
+ // AA level, AAA level
+ aa: ratio >= 4.5,
+ aaa: ratio >= 7,
+ // same but for 18pt+ texts
+ laa: ratio >= 3,
+ laaa: ratio >= 4.5
+ })
+ const colorsConverted = Object.entries(colors).reduce((acc, [key, value]) => ({ ...acc, [key]: colorConvert(value) }), {})
+
+ const ratios = Object.entries(SLOT_INHERITANCE).reduce((acc, [key, value]) => {
+ const slotIsBaseText = key === 'text' || key === 'link'
+ const slotIsText = slotIsBaseText || (
+ typeof value === 'object' && value !== null && value.textColor
+ )
+ if (!slotIsText) return acc
+ const { layer, variant } = slotIsBaseText ? { layer: 'bg' } : value
+ const background = variant || layer
+ const opacitySlot = getOpacitySlot(background)
+ const textColors = [
+ key,
+ ...(background === 'bg' ? ['cRed', 'cGreen', 'cBlue', 'cOrange'] : [])
+ ]
+
+ const layers = getLayers(
+ layer,
+ variant || layer,
+ opacitySlot,
+ colorsConverted,
+ opacity
+ )
+
+ return {
+ ...acc,
+ ...textColors.reduce((acc, textColorKey) => {
+ const newKey = slotIsBaseText
+ ? 'bg' + textColorKey[0].toUpperCase() + textColorKey.slice(1)
+ : textColorKey
+ return {
+ ...acc,
+ [newKey]: getContrastRatioLayers(
+ colorsConverted[textColorKey],
+ layers,
+ colorsConverted[textColorKey]
+ )
+ }
+ }, {})
+ }
+ }, {})
+
+ return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {})
+ } catch (e) {
+ console.warn('Failure computing contrasts', e)
}
-
- return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {})
},
previewRules () {
if (!this.preview.rules) return ''
@@ -272,7 +295,7 @@ export default {
].join(';')
},
shadowsAvailable () {
- return Object.keys(this.previewTheme.shadows).sort()
+ return Object.keys(DEFAULT_SHADOWS).sort()
},
currentShadowOverriden: {
get () {
@@ -287,7 +310,7 @@ export default {
}
},
currentShadowFallback () {
- return this.previewTheme.shadows[this.shadowSelected]
+ return (this.previewTheme.shadows || {})[this.shadowSelected]
},
currentShadow: {
get () {
@@ -309,27 +332,34 @@ export default {
!this.keepColor
)
- const theme = {}
+ const source = {
+ themeEngineVersion: CURRENT_VERSION
+ }
if (this.keepFonts || saveEverything) {
- theme.fonts = this.fontsLocal
+ source.fonts = this.fontsLocal
}
if (this.keepShadows || saveEverything) {
- theme.shadows = this.shadowsLocal
+ source.shadows = this.shadowsLocal
}
if (this.keepOpacity || saveEverything) {
- theme.opacity = this.currentOpacity
+ source.opacity = this.currentOpacity
}
if (this.keepColor || saveEverything) {
- theme.colors = this.currentColors
+ source.colors = this.currentColors
}
if (this.keepRoundness || saveEverything) {
- theme.radii = this.currentRadii
+ source.radii = this.currentRadii
+ }
+
+ const theme = {
+ themeEngineVersion: CURRENT_VERSION,
+ ...this.previewTheme
}
return {
- // To separate from other random JSON files and possible future theme formats
- _pleroma_theme_version: 2, theme
+ // To separate from other random JSON files and possible future source formats
+ _pleroma_theme_version: 2, theme, source
}
}
},
@@ -346,10 +376,128 @@ export default {
Checkbox
},
methods: {
+ loadTheme (
+ {
+ theme,
+ source,
+ _pleroma_theme_version: fileVersion
+ },
+ origin,
+ forceUseSource = false
+ ) {
+ this.dismissWarning()
+ if (!source && !theme) {
+ throw new Error('Can\'t load theme: empty')
+ }
+ const version = (origin === 'localStorage' && !theme.colors)
+ ? 'l1'
+ : fileVersion
+ const snapshotEngineVersion = (theme || {}).themeEngineVersion
+ const themeEngineVersion = (source || {}).themeEngineVersion || 2
+ const versionsMatch = themeEngineVersion === CURRENT_VERSION
+ const sourceSnapshotMismatch = (
+ theme !== undefined &&
+ source !== undefined &&
+ themeEngineVersion !== snapshotEngineVersion
+ )
+ // Force loading of source if user requested it or if snapshot
+ // is unavailable
+ const forcedSourceLoad = (source && forceUseSource) || !theme
+ if (!(versionsMatch && !sourceSnapshotMismatch) &&
+ !forcedSourceLoad &&
+ version !== 'l1' &&
+ origin !== 'defaults'
+ ) {
+ if (sourceSnapshotMismatch && origin === 'localStorage') {
+ this.themeWarning = {
+ origin,
+ themeEngineVersion,
+ type: 'snapshot_source_mismatch'
+ }
+ } else if (!theme) {
+ this.themeWarning = {
+ origin,
+ noActionsPossible: true,
+ themeEngineVersion,
+ type: 'no_snapshot_old_version'
+ }
+ } else if (!versionsMatch) {
+ this.themeWarning = {
+ origin,
+ noActionsPossible: !source,
+ themeEngineVersion,
+ type: 'wrong_version'
+ }
+ }
+ }
+ this.normalizeLocalState(theme, version, source, forcedSourceLoad)
+ },
+ forceLoadLocalStorage () {
+ this.loadThemeFromLocalStorage(true)
+ },
+ dismissWarning () {
+ this.themeWarning = undefined
+ this.tempImportFile = undefined
+ },
+ forceLoad () {
+ const { origin } = this.themeWarning
+ switch (origin) {
+ case 'localStorage':
+ this.loadThemeFromLocalStorage(true)
+ break
+ case 'file':
+ this.onImport(this.tempImportFile, true)
+ break
+ }
+ this.dismissWarning()
+ },
+ forceSnapshot () {
+ const { origin } = this.themeWarning
+ switch (origin) {
+ case 'localStorage':
+ this.loadThemeFromLocalStorage(false, true)
+ break
+ case 'file':
+ console.err('Forcing snapshout from file is not supported yet')
+ break
+ }
+ this.dismissWarning()
+ },
+ loadThemeFromLocalStorage (confirmLoadSource = false, forceSnapshot = false) {
+ const {
+ customTheme: theme,
+ customThemeSource: source
+ } = this.$store.getters.mergedConfig
+ if (!theme && !source) {
+ // Anon user or never touched themes
+ this.loadTheme(
+ this.$store.state.instance.themeData,
+ 'defaults',
+ confirmLoadSource
+ )
+ } else {
+ this.loadTheme(
+ {
+ theme,
+ source: forceSnapshot ? theme : source
+ },
+ 'localStorage',
+ confirmLoadSource
+ )
+ }
+ },
setCustomTheme () {
this.$store.dispatch('setOption', {
name: 'customTheme',
value: {
+ themeEngineVersion: CURRENT_VERSION,
+ ...this.previewTheme
+ }
+ })
+ this.$store.dispatch('setOption', {
+ name: 'customThemeSource',
+ value: {
+ themeEngineVersion: CURRENT_VERSION,
shadows: this.shadowsLocal,
fonts: this.fontsLocal,
opacity: this.currentOpacity,
@@ -358,21 +506,27 @@ export default {
}
})
},
- onImport (parsed) {
- if (parsed._pleroma_theme_version === 1) {
- this.normalizeLocalState(parsed, 1)
- } else if (parsed._pleroma_theme_version === 2) {
- this.normalizeLocalState(parsed.theme, 2)
- }
+ updatePreviewColorsAndShadows () {
+ this.previewColors = generateColors({
+ opacity: this.currentOpacity,
+ colors: this.currentColors
+ })
+ this.previewShadows = generateShadows(
+ { shadows: this.shadowsLocal, opacity: this.previewTheme.opacity, themeEngineVersion: this.engineVersion },
+ this.previewColors.theme.colors,
+ this.previewColors.mod
+ )
+ },
+ onImport (parsed, forceSource = false) {
+ this.tempImportFile = parsed
+ this.loadTheme(parsed, 'file', forceSource)
},
importValidator (parsed) {
const version = parsed._pleroma_theme_version
return version >= 1 || version <= 2
},
clearAll () {
- const state = this.$store.getters.mergedConfig.customTheme
- const version = state.colors ? 2 : 'l1'
- this.normalizeLocalState(this.$store.getters.mergedConfig.customTheme, version)
+ this.loadThemeFromLocalStorage()
},
// Clears all the extra stuff when loading V1 theme
@@ -411,19 +565,37 @@ export default {
/**
* This applies stored theme data onto form. Supports three versions of data:
+ * v3 (version >= 3) - newest version of themes which supports snapshots for better compatiblity
* v2 (version = 2) - newer version of themes.
* v1 (version = 1) - older version of themes (import from file)
* v1l (version = l1) - older version of theme (load from local storage)
* v1 and v1l differ because of way themes were stored/exported.
- * @param {Object} input - input data
+ * @param {Object} theme - theme data (snapshot)
* @param {Number} version - version of data. 0 means try to guess based on data. "l1" means v1, locastorage type
+ * @param {Object} source - theme source - this will be used if compatible
+ * @param {Boolean} source - by default source won't be used if version doesn't match since it might render differently
+ * this allows importing source anyway
*/
- normalizeLocalState (input, version = 0) {
- const colors = input.colors || input
+ normalizeLocalState (theme, version = 0, source, forceSource = false) {
+ let input
+ if (typeof source !== 'undefined') {
+ if (forceSource || source.themeEngineVersion === CURRENT_VERSION) {
+ input = source
+ version = source.themeEngineVersion
+ } else {
+ input = theme
+ }
+ } else {
+ input = theme
+ }
+
const radii = input.radii || input
const opacity = input.opacity
const shadows = input.shadows || {}
const fonts = input.fonts || {}
+ const colors = !input.themeEngineVersion
+ ? colors2to3(input.colors || input)
+ : input.colors || input
if (version === 0) {
if (input.version) version = input.version
@@ -437,6 +609,8 @@ export default {
}
}
+ this.engineVersion = version
+
// Stuff that differs between V1 and V2
if (version === 1) {
this.fgColorLocal = rgb2hex(colors.btn)
@@ -445,7 +619,7 @@ export default {
if (!this.keepColor) {
this.clearV1()
- const keys = new Set(version !== 1 ? Object.keys(colors) : [])
+ const keys = new Set(version !== 1 ? Object.keys(SLOT_INHERITANCE) : [])
if (version === 1 || version === 'l1') {
keys
.add('bg')
@@ -457,7 +631,17 @@ export default {
}
keys.forEach(key => {
- this[key + 'ColorLocal'] = rgb2hex(colors[key])
+ const color = colors[key]
+ const hex = rgb2hex(colors[key])
+ this[key + 'ColorLocal'] = hex === '#aN' ? color : hex
+ })
+ }
+
+ if (opacity && !this.keepOpacity) {
+ this.clearOpacity()
+ Object.entries(opacity).forEach(([k, v]) => {
+ if (typeof v === 'undefined' || v === null || Number.isNaN(v)) return
+ this[k + 'OpacityLocal'] = v
})
}
@@ -472,7 +656,11 @@ export default {
if (!this.keepShadows) {
this.clearShadows()
- this.shadowsLocal = shadows
+ if (version === 2) {
+ this.shadowsLocal = shadows2to3(shadows, this.previewTheme.opacity)
+ } else {
+ this.shadowsLocal = shadows
+ }
this.shadowSelected = this.shadowsAvailable[0]
}
@@ -480,14 +668,6 @@ export default {
this.clearFonts()
this.fontsLocal = fonts
}
-
- if (opacity && !this.keepOpacity) {
- this.clearOpacity()
- Object.entries(opacity).forEach(([k, v]) => {
- if (typeof v === 'undefined' || v === null || Number.isNaN(v)) return
- this[k + 'OpacityLocal'] = v
- })
- }
}
},
watch: {
@@ -502,8 +682,9 @@ export default {
},
shadowsLocal: {
handler () {
+ if (Object.getOwnPropertyNames(this.previewColors).length === 1) return
try {
- this.previewShadows = generateShadows({ shadows: this.shadowsLocal })
+ this.updatePreviewColorsAndShadows()
this.shadowsInvalid = false
} catch (e) {
this.shadowsInvalid = true
@@ -526,27 +707,24 @@ export default {
},
currentColors () {
try {
- this.previewColors = generateColors({
- opacity: this.currentOpacity,
- colors: this.currentColors
- })
+ this.updatePreviewColorsAndShadows()
this.colorsInvalid = false
+ this.shadowsInvalid = false
} catch (e) {
this.colorsInvalid = true
+ this.shadowsInvalid = true
console.warn(e)
}
},
currentOpacity () {
try {
- this.previewColors = generateColors({
- opacity: this.currentOpacity,
- colors: this.currentColors
- })
+ this.updatePreviewColorsAndShadows()
} catch (e) {
console.warn(e)
}
},
selected () {
+ this.dismissWarning()
if (this.selectedVersion === 1) {
if (!this.keepRoundness) {
this.clearRoundness()
@@ -573,7 +751,7 @@ export default {
this.cOrangeColorLocal = this.selected[8]
}
} else if (this.selectedVersion >= 2) {
- this.normalizeLocalState(this.selected.theme, 2)
+ this.normalizeLocalState(this.selected.theme, 2, this.selected.source)
}
}
}
diff --git a/src/components/style_switcher/style_switcher.scss b/src/components/style_switcher/style_switcher.scss
index 135c113a..d2a40d13 100644
--- a/src/components/style_switcher/style_switcher.scss
+++ b/src/components/style_switcher/style_switcher.scss
@@ -1,5 +1,15 @@
@import '../../_variables.scss';
.style-switcher {
+ .theme-warning {
+ display: flex;
+ align-items: baseline;
+ margin-bottom: .5em;
+ .buttons {
+ .btn {
+ margin-bottom: .5em;
+ }
+ }
+ }
.preset-switcher {
margin-right: 1em;
}
@@ -15,26 +25,23 @@
&.disabled {
input, select {
- &:not(.exclude-disabled) {
- opacity: .5
- }
+ opacity: .5
}
}
+ .opt {
+ margin: .5em;
+ }
+
+ .color-input {
+ flex: 0 0 0;
+ }
+
input, select {
min-width: 3em;
margin: 0;
flex: 0;
- &[type=color] {
- padding: 1px;
- cursor: pointer;
- height: 29px;
- min-width: 2em;
- border: none;
- align-self: stretch;
- }
-
&[type=number] {
min-width: 5em;
}
@@ -42,13 +49,6 @@
&[type=range] {
flex: 1;
min-width: 3em;
- }
-
- &[type=checkbox] + label {
- margin: 6px 0;
- }
-
- &:not([type=number]):not([type=text]) {
align-self: flex-start;
}
}
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index ad032041..62c8e634 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -2,7 +2,53 @@
<div class="style-switcher">
<div class="presets-container">
<div class="save-load">
- <export-import
+ <div
+ v-if="themeWarning"
+ class="theme-warning"
+ >
+ <div class="alert warning">
+ {{ themeWarningHelp }}
+ </div>
+ <div class="buttons">
+ <template v-if="themeWarning.type === 'snapshot_source_mismatch'">
+ <button
+ class="btn"
+ @click="forceLoad"
+ >
+ {{ $t('settings.style.switcher.use_source') }}
+ </button>
+ <button
+ class="btn"
+ @click="forceSnapshot"
+ >
+ {{ $t('settings.style.switcher.use_snapshot') }}
+ </button>
+ </template>
+ <template v-else-if="themeWarning.noActionsPossible">
+ <button
+ class="btn"
+ @click="dismissWarning"
+ >
+ {{ $t('general.dismiss') }}
+ </button>
+ </template>
+ <template v-else>
+ <button
+ class="btn"
+ @click="forceLoad"
+ >
+ {{ $t('settings.style.switcher.load_theme') }}
+ </button>
+ <button
+ class="btn"
+ @click="dismissWarning"
+ >
+ {{ $t('settings.style.switcher.keep_as_is') }}
+ </button>
+ </template>
+ </div>
+ </div>
+ <ExportImport
:export-object="exportedTheme"
:export-label="$t(&quot;settings.export_theme&quot;)"
:import-label="$t(&quot;settings.import_theme&quot;)"
@@ -27,8 +73,8 @@
:key="style.name"
:value="style"
:style="{
- backgroundColor: style[1] || style.theme.colors.bg,
- color: style[3] || style.theme.colors.text
+ backgroundColor: style[1] || (style.theme || style.source).colors.bg,
+ color: style[3] || (style.theme || style.source).colors.text
}"
>
{{ style[0] || style.name }}
@@ -38,7 +84,7 @@
</label>
</div>
</template>
- </export-import>
+ </ExportImport>
</div>
<div class="save-load-options">
<span class="keep-option">
@@ -70,9 +116,7 @@
</div>
</div>
- <div class="preview-container">
- <preview :style="previewRules" />
- </div>
+ <preview :style="previewRules" />
<keep-alive>
<tab-switcher key="style-tweak">
@@ -106,7 +150,7 @@
<OpacityInput
v-model="bgOpacityLocal"
name="bgOpacity"
- :fallback="previewTheme.opacity.bg || 1"
+ :fallback="previewTheme.opacity.bg"
/>
<ColorInput
v-model="textColorLocal"
@@ -115,9 +159,18 @@
/>
<ContrastRatio :contrast="previewContrast.bgText" />
<ColorInput
+ v-model="accentColorLocal"
+ name="accentColor"
+ :fallback="previewTheme.colors.link"
+ :label="$t('settings.accent')"
+ :show-optional-tickbox="typeof linkColorLocal !== 'undefined'"
+ />
+ <ColorInput
v-model="linkColorLocal"
name="linkColor"
+ :fallback="previewTheme.colors.accent"
:label="$t('settings.links')"
+ :show-optional-tickbox="typeof accentColorLocal !== 'undefined'"
/>
<ContrastRatio :contrast="previewContrast.bgLink" />
</div>
@@ -148,13 +201,13 @@
name="cRedColor"
:label="$t('settings.cRed')"
/>
- <ContrastRatio :contrast="previewContrast.bgRed" />
+ <ContrastRatio :contrast="previewContrast.bgCRed" />
<ColorInput
v-model="cBlueColorLocal"
name="cBlueColor"
:label="$t('settings.cBlue')"
/>
- <ContrastRatio :contrast="previewContrast.bgBlue" />
+ <ContrastRatio :contrast="previewContrast.bgCBlue" />
</div>
<div class="color-item">
<ColorInput
@@ -162,13 +215,13 @@
name="cGreenColor"
:label="$t('settings.cGreen')"
/>
- <ContrastRatio :contrast="previewContrast.bgGreen" />
+ <ContrastRatio :contrast="previewContrast.bgCGreen" />
<ColorInput
v-model="cOrangeColorLocal"
name="cOrangeColor"
:label="$t('settings.cOrange')"
/>
- <ContrastRatio :contrast="previewContrast.bgOrange" />
+ <ContrastRatio :contrast="previewContrast.bgCOrange" />
</div>
<p>{{ $t('settings.theme_help_v2_2') }}</p>
</div>
@@ -193,6 +246,14 @@
</button>
</div>
<div class="color-item">
+ <h4>{{ $t('settings.style.advanced_colors.post') }}</h4>
+ <ColorInput
+ v-model="postLinkColorLocal"
+ name="postLinkColor"
+ :fallback="previewTheme.colors.accent"
+ :label="$t('settings.links')"
+ />
+ <ContrastRatio :contrast="previewContrast.postLink" />
<h4>{{ $t('settings.style.advanced_colors.alert') }}</h4>
<ColorInput
v-model="alertErrorColorLocal"
@@ -200,14 +261,53 @@
:label="$t('settings.style.advanced_colors.alert_error')"
:fallback="previewTheme.colors.alertError"
/>
- <ContrastRatio :contrast="previewContrast.alertError" />
+ <ColorInput
+ v-model="alertErrorTextColorLocal"
+ name="alertErrorText"
+ :label="$t('settings.text')"
+ :fallback="previewTheme.colors.alertErrorText"
+ />
+ <ContrastRatio
+ :contrast="previewContrast.alertErrorText"
+ large="true"
+ />
<ColorInput
v-model="alertWarningColorLocal"
name="alertWarning"
:label="$t('settings.style.advanced_colors.alert_warning')"
:fallback="previewTheme.colors.alertWarning"
/>
- <ContrastRatio :contrast="previewContrast.alertWarning" />
+ <ColorInput
+ v-model="alertWarningTextColorLocal"
+ name="alertWarningText"
+ :label="$t('settings.text')"
+ :fallback="previewTheme.colors.alertWarningText"
+ />
+ <ContrastRatio
+ :contrast="previewContrast.alertWarningText"
+ large="true"
+ />
+ <ColorInput
+ v-model="alertNeutralColorLocal"
+ name="alertNeutral"
+ :label="$t('settings.style.advanced_colors.alert_neutral')"
+ :fallback="previewTheme.colors.alertNeutral"
+ />
+ <ColorInput
+ v-model="alertNeutralTextColorLocal"
+ name="alertNeutralText"
+ :label="$t('settings.text')"
+ :fallback="previewTheme.colors.alertNeutralText"
+ />
+ <ContrastRatio
+ :contrast="previewContrast.alertNeutralText"
+ large="true"
+ />
+ <OpacityInput
+ v-model="alertOpacityLocal"
+ name="alertOpacity"
+ :fallback="previewTheme.opacity.alert"
+ />
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.badge') }}</h4>
@@ -217,19 +317,30 @@
:label="$t('settings.style.advanced_colors.badge_notification')"
:fallback="previewTheme.colors.badgeNotification"
/>
+ <ColorInput
+ v-model="badgeNotificationTextColorLocal"
+ name="badgeNotificationText"
+ :label="$t('settings.text')"
+ :fallback="previewTheme.colors.badgeNotificationText"
+ />
+ <ContrastRatio
+ :contrast="previewContrast.badgeNotificationText"
+ large="true"
+ />
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.panel_header') }}</h4>
<ColorInput
v-model="panelColorLocal"
name="panelColor"
- :fallback="fgColorLocal"
+ :fallback="previewTheme.colors.panel"
:label="$t('settings.background')"
/>
<OpacityInput
v-model="panelOpacityLocal"
name="panelOpacity"
- :fallback="previewTheme.opacity.panel || 1"
+ :fallback="previewTheme.opacity.panel"
+ :disabled="panelColorLocal === 'transparent'"
/>
<ColorInput
v-model="panelTextColorLocal"
@@ -239,7 +350,7 @@
/>
<ContrastRatio
:contrast="previewContrast.panelText"
- large="1"
+ large="true"
/>
<ColorInput
v-model="panelLinkColorLocal"
@@ -249,7 +360,7 @@
/>
<ContrastRatio
:contrast="previewContrast.panelLink"
- large="1"
+ large="true"
/>
</div>
<div class="color-item">
@@ -257,7 +368,7 @@
<ColorInput
v-model="topBarColorLocal"
name="topBarColor"
- :fallback="fgColorLocal"
+ :fallback="previewTheme.colors.topBar"
:label="$t('settings.background')"
/>
<ColorInput
@@ -280,13 +391,14 @@
<ColorInput
v-model="inputColorLocal"
name="inputColor"
- :fallback="fgColorLocal"
+ :fallback="previewTheme.colors.input"
:label="$t('settings.background')"
/>
<OpacityInput
v-model="inputOpacityLocal"
name="inputOpacity"
- :fallback="previewTheme.opacity.input || 1"
+ :fallback="previewTheme.opacity.input"
+ :disabled="inputColorLocal === 'transparent'"
/>
<ColorInput
v-model="inputTextColorLocal"
@@ -301,13 +413,14 @@
<ColorInput
v-model="btnColorLocal"
name="btnColor"
- :fallback="fgColorLocal"
+ :fallback="previewTheme.colors.btn"
:label="$t('settings.background')"
/>
<OpacityInput
v-model="btnOpacityLocal"
name="btnOpacity"
- :fallback="previewTheme.opacity.btn || 1"
+ :fallback="previewTheme.opacity.btn"
+ :disabled="btnColorLocal === 'transparent'"
/>
<ColorInput
v-model="btnTextColorLocal"
@@ -316,6 +429,124 @@
:label="$t('settings.text')"
/>
<ContrastRatio :contrast="previewContrast.btnText" />
+ <ColorInput
+ v-model="btnPanelTextColorLocal"
+ name="btnPanelTextColor"
+ :fallback="previewTheme.colors.btnPanelText"
+ :label="$t('settings.style.advanced_colors.panel_header')"
+ />
+ <ContrastRatio :contrast="previewContrast.btnPanelText" />
+ <ColorInput
+ v-model="btnTopBarTextColorLocal"
+ name="btnTopBarTextColor"
+ :fallback="previewTheme.colors.btnTopBarText"
+ :label="$t('settings.style.advanced_colors.top_bar')"
+ />
+ <ContrastRatio :contrast="previewContrast.btnTopBarText" />
+ <h5>{{ $t('settings.style.advanced_colors.pressed') }}</h5>
+ <ColorInput
+ v-model="btnPressedColorLocal"
+ name="btnPressedColor"
+ :fallback="previewTheme.colors.btnPressed"
+ :label="$t('settings.background')"
+ />
+ <ColorInput
+ v-model="btnPressedTextColorLocal"
+ name="btnPressedTextColor"
+ :fallback="previewTheme.colors.btnPressedText"
+ :label="$t('settings.text')"
+ />
+ <ContrastRatio :contrast="previewContrast.btnPressedText" />
+ <ColorInput
+ v-model="btnPressedPanelTextColorLocal"
+ name="btnPressedPanelTextColor"
+ :fallback="previewTheme.colors.btnPressedPanelText"
+ :label="$t('settings.style.advanced_colors.panel_header')"
+ />
+ <ContrastRatio :contrast="previewContrast.btnPressedPanelText" />
+ <ColorInput
+ v-model="btnPressedTopBarTextColorLocal"
+ name="btnPressedTopBarTextColor"
+ :fallback="previewTheme.colors.btnPressedTopBarText"
+ :label="$t('settings.style.advanced_colors.top_bar')"
+ />
+ <ContrastRatio :contrast="previewContrast.btnPressedTopBarText" />
+ <h5>{{ $t('settings.style.advanced_colors.disabled') }}</h5>
+ <ColorInput
+ v-model="btnDisabledColorLocal"
+ name="btnDisabledColor"
+ :fallback="previewTheme.colors.btnDisabled"
+ :label="$t('settings.background')"
+ />
+ <ColorInput
+ v-model="btnDisabledTextColorLocal"
+ name="btnDisabledTextColor"
+ :fallback="previewTheme.colors.btnDisabledText"
+ :label="$t('settings.text')"
+ />
+ <ColorInput
+ v-model="btnDisabledPanelTextColorLocal"
+ name="btnDisabledPanelTextColor"
+ :fallback="previewTheme.colors.btnDisabledPanelText"
+ :label="$t('settings.style.advanced_colors.panel_header')"
+ />
+ <ColorInput
+ v-model="btnDisabledTopBarTextColorLocal"
+ name="btnDisabledTopBarTextColor"
+ :fallback="previewTheme.colors.btnDisabledTopBarText"
+ :label="$t('settings.style.advanced_colors.top_bar')"
+ />
+ <h5>{{ $t('settings.style.advanced_colors.toggled') }}</h5>
+ <ColorInput
+ v-model="btnToggledColorLocal"
+ name="btnToggledColor"
+ :fallback="previewTheme.colors.btnToggled"
+ :label="$t('settings.background')"
+ />
+ <ColorInput
+ v-model="btnToggledTextColorLocal"
+ name="btnToggledTextColor"
+ :fallback="previewTheme.colors.btnToggledText"
+ :label="$t('settings.text')"
+ />
+ <ContrastRatio :contrast="previewContrast.btnToggledText" />
+ <ColorInput
+ v-model="btnToggledPanelTextColorLocal"
+ name="btnToggledPanelTextColor"
+ :fallback="previewTheme.colors.btnToggledPanelText"
+ :label="$t('settings.style.advanced_colors.panel_header')"
+ />
+ <ContrastRatio :contrast="previewContrast.btnToggledPanelText" />
+ <ColorInput
+ v-model="btnToggledTopBarTextColorLocal"
+ name="btnToggledTopBarTextColor"
+ :fallback="previewTheme.colors.btnToggledTopBarText"
+ :label="$t('settings.style.advanced_colors.top_bar')"
+ />
+ <ContrastRatio :contrast="previewContrast.btnToggledTopBarText" />
+ </div>
+ <div class="color-item">
+ <h4>{{ $t('settings.style.advanced_colors.tabs') }}</h4>
+ <ColorInput
+ v-model="tabColorLocal"
+ name="tabColor"
+ :fallback="previewTheme.colors.tab"
+ :label="$t('settings.background')"
+ />
+ <ColorInput
+ v-model="tabTextColorLocal"
+ name="tabTextColor"
+ :fallback="previewTheme.colors.tabText"
+ :label="$t('settings.text')"
+ />
+ <ContrastRatio :contrast="previewContrast.tabText" />
+ <ColorInput
+ v-model="tabActiveTextColorLocal"
+ name="tabActiveTextColor"
+ :fallback="previewTheme.colors.tabActiveText"
+ :label="$t('settings.text')"
+ />
+ <ContrastRatio :contrast="previewContrast.tabActiveText" />
</div>
<div class="color-item">
<h4>{{ $t('settings.style.advanced_colors.borders') }}</h4>
@@ -328,7 +559,8 @@
<OpacityInput
v-model="borderOpacityLocal"
name="borderOpacity"
- :fallback="previewTheme.opacity.border || 1"
+ :fallback="previewTheme.opacity.border"
+ :disabled="borderColorLocal === 'transparent'"
/>
</div>
<div class="color-item">
@@ -336,7 +568,7 @@
<ColorInput
v-model="faintColorLocal"
name="faintColor"
- :fallback="previewTheme.colors.faint || 1"
+ :fallback="previewTheme.colors.faint"
:label="$t('settings.text')"
/>
<ColorInput
@@ -354,8 +586,145 @@
<OpacityInput
v-model="faintOpacityLocal"
name="faintOpacity"
- :fallback="previewTheme.opacity.faint || 0.5"
+ :fallback="previewTheme.opacity.faint"
+ />
+ </div>
+ <div class="color-item">
+ <h4>{{ $t('settings.style.advanced_colors.underlay') }}</h4>
+ <ColorInput
+ v-model="underlayColorLocal"
+ name="underlay"
+ :label="$t('settings.style.advanced_colors.underlay')"
+ :fallback="previewTheme.colors.underlay"
+ />
+ <OpacityInput
+ v-model="underlayOpacityLocal"
+ name="underlayOpacity"
+ :fallback="previewTheme.opacity.underlay"
+ :disabled="underlayOpacityLocal === 'transparent'"
+ />
+ </div>
+ <div class="color-item">
+ <h4>{{ $t('settings.style.advanced_colors.poll') }}</h4>
+ <ColorInput
+ v-model="pollColorLocal"
+ name="poll"
+ :label="$t('settings.background')"
+ :fallback="previewTheme.colors.poll"
+ />
+ <ColorInput
+ v-model="pollTextColorLocal"
+ name="pollText"
+ :label="$t('settings.text')"
+ :fallback="previewTheme.colors.pollText"
+ />
+ </div>
+ <div class="color-item">
+ <h4>{{ $t('settings.style.advanced_colors.icons') }}</h4>
+ <ColorInput
+ v-model="iconColorLocal"
+ name="icon"
+ :label="$t('settings.style.advanced_colors.icons')"
+ :fallback="previewTheme.colors.icon"
+ />
+ </div>
+ <div class="color-item">
+ <h4>{{ $t('settings.style.advanced_colors.highlight') }}</h4>
+ <ColorInput
+ v-model="highlightColorLocal"
+ name="highlight"
+ :label="$t('settings.background')"
+ :fallback="previewTheme.colors.highlight"
+ />
+ <ColorInput
+ v-model="highlightTextColorLocal"
+ name="highlightText"
+ :label="$t('settings.text')"
+ :fallback="previewTheme.colors.highlightText"
+ />
+ <ContrastRatio :contrast="previewContrast.highlightText" />
+ <ColorInput
+ v-model="highlightLinkColorLocal"
+ name="highlightLink"
+ :label="$t('settings.links')"
+ :fallback="previewTheme.colors.highlightLink"
+ />
+ <ContrastRatio :contrast="previewContrast.highlightLink" />
+ </div>
+ <div class="color-item">
+ <h4>{{ $t('settings.style.advanced_colors.popover') }}</h4>
+ <ColorInput
+ v-model="popoverColorLocal"
+ name="popover"
+ :label="$t('settings.background')"
+ :fallback="previewTheme.colors.popover"
+ />
+ <OpacityInput
+ v-model="popoverOpacityLocal"
+ name="popoverOpacity"
+ :fallback="previewTheme.opacity.popover"
+ :disabled="popoverOpacityLocal === 'transparent'"
+ />
+ <ColorInput
+ v-model="popoverTextColorLocal"
+ name="popoverText"
+ :label="$t('settings.text')"
+ :fallback="previewTheme.colors.popoverText"
+ />
+ <ContrastRatio :contrast="previewContrast.popoverText" />
+ <ColorInput
+ v-model="popoverLinkColorLocal"
+ name="popoverLink"
+ :label="$t('settings.links')"
+ :fallback="previewTheme.colors.popoverLink"
+ />
+ <ContrastRatio :contrast="previewContrast.popoverLink" />
+ </div>
+ <div class="color-item">
+ <h4>{{ $t('settings.style.advanced_colors.selectedPost') }}</h4>
+ <ColorInput
+ v-model="selectedPostColorLocal"
+ name="selectedPost"
+ :label="$t('settings.background')"
+ :fallback="previewTheme.colors.selectedPost"
+ />
+ <ColorInput
+ v-model="selectedPostTextColorLocal"
+ name="selectedPostText"
+ :label="$t('settings.text')"
+ :fallback="previewTheme.colors.selectedPostText"
+ />
+ <ContrastRatio :contrast="previewContrast.selectedPostText" />
+ <ColorInput
+ v-model="selectedPostLinkColorLocal"
+ name="selectedPostLink"
+ :label="$t('settings.links')"
+ :fallback="previewTheme.colors.selectedPostLink"
+ />
+ <ContrastRatio :contrast="previewContrast.selectedPostLink" />
+ </div>
+ <div class="color-item">
+ <h4>{{ $t('settings.style.advanced_colors.selectedMenu') }}</h4>
+ <ColorInput
+ v-model="selectedMenuColorLocal"
+ name="selectedMenu"
+ :label="$t('settings.background')"
+ :fallback="previewTheme.colors.selectedMenu"
+ />
+ <ColorInput
+ v-model="selectedMenuTextColorLocal"
+ name="selectedMenuText"
+ :label="$t('settings.text')"
+ :fallback="previewTheme.colors.selectedMenuText"
+ />
+ <ContrastRatio :contrast="previewContrast.selectedMenuText" />
+ <ColorInput
+ v-model="selectedMenuLinkColorLocal"
+ name="selectedMenuLink"
+ :label="$t('settings.links')"
+ :fallback="previewTheme.colors.selectedMenuLink"
/>
+ <ContrastRatio :contrast="previewContrast.selectedMenuLink" />
</div>
</div>
@@ -491,7 +860,7 @@
{{ $t('settings.style.switcher.clear_all') }}
</button>
</div>
- <shadow-control
+ <ShadowControl
v-model="currentShadow"
:ready="!!currentShadowFallback"
:fallback="currentShadowFallback"