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/style_switcher.js599
-rw-r--r--src/components/style_switcher/style_switcher.scss275
-rw-r--r--src/components/style_switcher/style_switcher.vue563
3 files changed, 1075 insertions, 362 deletions
diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js
index 95c15b49..d833341f 100644
--- a/src/components/style_switcher/style_switcher.js
+++ b/src/components/style_switcher/style_switcher.js
@@ -1,4 +1,25 @@
-import { rgbstr2hex } from '../../services/color_convert/color_convert.js'
+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 } from '../../services/style_setter/style_setter.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'
+import ShadowControl from '../shadow_control/shadow_control.vue'
+import FontControl from '../font_control/font_control.vue'
+import ContrastRatio from '../contrast_ratio/contrast_ratio.vue'
+import TabSwitcher from '../tab_switcher/tab_switcher.jsx'
+
+// List of color values used in v1
+const v1OnlyNames = [
+ 'bg',
+ 'fg',
+ 'text',
+ 'link',
+ 'cRed',
+ 'cGreen',
+ 'cBlue',
+ 'cOrange'
+].map(_ => _ + 'ColorLocal')
export default {
data () {
@@ -6,16 +27,72 @@ export default {
availableStyles: [],
selected: this.$store.state.config.theme,
invalidThemeImported: false,
- bgColorLocal: '',
- btnColorLocal: '',
+
+ previewShadows: {},
+ previewColors: {},
+ previewRadii: {},
+ previewFonts: {},
+
+ shadowsInvalid: true,
+ colorsInvalid: true,
+ radiiInvalid: true,
+
+ keepShadows: false,
+ keepOpacity: false,
+ keepRoundness: false,
+ keepFonts: false,
+
textColorLocal: '',
linkColorLocal: '',
- redColorLocal: '',
- blueColorLocal: '',
- greenColorLocal: '',
- orangeColorLocal: '',
+
+ bgColorLocal: '',
+ bgOpacityLocal: undefined,
+
+ fgColorLocal: '',
+ fgTextColorLocal: undefined,
+ fgLinkColorLocal: undefined,
+
+ btnColorLocal: undefined,
+ btnTextColorLocal: undefined,
+ btnOpacityLocal: undefined,
+
+ inputColorLocal: undefined,
+ inputTextColorLocal: undefined,
+ inputOpacityLocal: undefined,
+
+ panelColorLocal: undefined,
+ panelTextColorLocal: undefined,
+ panelFaintColorLocal: undefined,
+ panelOpacityLocal: undefined,
+
+ topBarColorLocal: undefined,
+ topBarTextColorLocal: undefined,
+ topBarLinkColorLocal: undefined,
+
+ alertErrorColorLocal: undefined,
+
+ badgeOpacityLocal: undefined,
+ badgeNotificationColorLocal: undefined,
+
+ borderColorLocal: undefined,
+ borderOpacityLocal: undefined,
+
+ faintColorLocal: undefined,
+ faintOpacityLocal: undefined,
+ faintLinkColorLocal: undefined,
+
+ cRedColorLocal: '',
+ cBlueColorLocal: '',
+ cGreenColorLocal: '',
+ cOrangeColorLocal: '',
+
+ shadowSelected: undefined,
+ shadowsLocal: {},
+ fontsLocal: {},
+
btnRadiusLocal: '',
inputRadiusLocal: '',
+ checkboxRadiusLocal: '',
panelRadiusLocal: '',
avatarRadiusLocal: '',
avatarAltRadiusLocal: '',
@@ -29,19 +106,238 @@ export default {
window.fetch('/static/styles.json')
.then((data) => data.json())
.then((themes) => {
- self.availableStyles = themes
+ return Promise.all(Object.entries(themes).map(([k, v]) => {
+ if (typeof v === 'object') {
+ return Promise.resolve([k, v])
+ } else if (typeof v === 'string') {
+ return window.fetch(v)
+ .then((data) => data.json())
+ .then((theme) => {
+ return [k, theme]
+ })
+ .catch((e) => {
+ console.error(e)
+ return []
+ })
+ }
+ }))
+ })
+ .then((promises) => {
+ return promises
+ .filter(([k, v]) => v)
+ .reduce((acc, [k, v]) => {
+ acc[k] = v
+ return acc
+ }, {})
+ }).then((themesComplete) => {
+ self.availableStyles = themesComplete
})
},
mounted () {
- this.normalizeLocalState(this.$store.state.config.colors, this.$store.state.config.radii)
+ this.normalizeLocalState(this.$store.state.config.customTheme)
+ if (typeof this.shadowSelected === 'undefined') {
+ this.shadowSelected = this.shadowsAvailable[0]
+ }
+ },
+ computed: {
+ 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,
+ 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,
+ badgeNotification: this.badgeNotificationColorLocal,
+
+ faint: this.faintColorLocal,
+ faintLink: this.faintLinkColorLocal,
+ border: this.borderColorLocal,
+
+ cRed: this.cRedColorLocal,
+ cBlue: this.cBlueColorLocal,
+ cGreen: this.cGreenColorLocal,
+ cOrange: this.cOrangeColorLocal
+ }
+ },
+ currentOpacity () {
+ return {
+ bg: this.bgOpacityLocal,
+ btn: this.btnOpacityLocal,
+ input: this.inputOpacityLocal,
+ panel: this.panelOpacityLocal,
+ topBar: this.topBarOpacityLocal,
+ border: this.borderOpacityLocal,
+ faint: this.faintOpacityLocal
+ }
+ },
+ currentRadii () {
+ return {
+ btn: this.btnRadiusLocal,
+ input: this.inputRadiusLocal,
+ checkbox: this.checkboxRadiusLocal,
+ panel: this.panelRadiusLocal,
+ avatar: this.avatarRadiusLocal,
+ avatarAlt: this.avatarAltRadiusLocal,
+ tooltip: this.tooltipRadiusLocal,
+ attachment: this.attachmentRadiusLocal
+ }
+ },
+ 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
+ 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),
+ 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),
+ 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),
+
+ 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)
+ }
+
+ return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {})
+ },
+ previewRules () {
+ if (!this.preview.rules) return ''
+ return [
+ ...Object.values(this.preview.rules),
+ 'color: var(--text)',
+ 'font-family: var(--interfaceFont, sans-serif)'
+ ].join(';')
+ },
+ shadowsAvailable () {
+ return Object.keys(this.previewTheme.shadows).sort()
+ },
+ currentShadowOverriden: {
+ get () {
+ return !!this.currentShadow
+ },
+ set (val) {
+ if (val) {
+ set(this.shadowsLocal, this.shadowSelected, this.currentShadowFallback.map(_ => Object.assign({}, _)))
+ } else {
+ del(this.shadowsLocal, this.shadowSelected)
+ }
+ }
+ },
+ currentShadowFallback () {
+ return this.previewTheme.shadows[this.shadowSelected]
+ },
+ currentShadow: {
+ get () {
+ return this.shadowsLocal[this.shadowSelected]
+ },
+ set (v) {
+ set(this.shadowsLocal, this.shadowSelected, v)
+ }
+ },
+ themeValid () {
+ return !this.shadowsInvalid && !this.colorsInvalid && !this.radiiInvalid
+ }
+ },
+ components: {
+ ColorInput,
+ OpacityInput,
+ RangeInput,
+ ContrastRatio,
+ ShadowControl,
+ FontControl,
+ TabSwitcher
},
methods: {
exportCurrentTheme () {
const stringified = JSON.stringify({
// To separate from other random JSON files and possible future theme formats
- _pleroma_theme_version: 1,
- colors: this.$store.state.config.colors,
- radii: this.$store.state.config.radii
+ _pleroma_theme_version: 2,
+ theme: {
+ shadows: this.shadowsLocal,
+ fonts: this.fontsLocal,
+ opacity: this.currentOpacity,
+ colors: this.currentColors,
+ radii: this.currentRadii
+ }
}, null, 2) // Pretty-print and indent with 2 spaces
// Create an invisible link with a data url and simulate a click
@@ -69,7 +365,9 @@ export default {
try {
const parsed = JSON.parse(target.result)
if (parsed._pleroma_theme_version === 1) {
- this.normalizeLocalState(parsed.colors, parsed.radii)
+ this.normalizeLocalState(parsed, 1)
+ } else if (parsed._pleroma_theme_version === 2) {
+ this.normalizeLocalState(parsed.theme, 2)
} else {
// A theme from the future, spooky
this.invalidThemeImported = true
@@ -89,81 +387,214 @@ export default {
},
setCustomTheme () {
- if (!this.bgColorLocal && !this.btnColorLocal && !this.linkColorLocal) {
- // reset to picked themes
- }
-
- const rgb = (hex) => {
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
- return result ? {
- r: parseInt(result[1], 16),
- g: parseInt(result[2], 16),
- b: parseInt(result[3], 16)
- } : null
- }
- const bgRgb = rgb(this.bgColorLocal)
- const btnRgb = rgb(this.btnColorLocal)
- const textRgb = rgb(this.textColorLocal)
- const linkRgb = rgb(this.linkColorLocal)
-
- const redRgb = rgb(this.redColorLocal)
- const blueRgb = rgb(this.blueColorLocal)
- const greenRgb = rgb(this.greenColorLocal)
- const orangeRgb = rgb(this.orangeColorLocal)
-
- if (bgRgb && btnRgb && linkRgb) {
- this.$store.dispatch('setOption', {
- name: 'customTheme',
- value: {
- fg: btnRgb,
- bg: bgRgb,
- text: textRgb,
- link: linkRgb,
- cRed: redRgb,
- cBlue: blueRgb,
- cGreen: greenRgb,
- cOrange: orangeRgb,
- btnRadius: this.btnRadiusLocal,
- inputRadius: this.inputRadiusLocal,
- panelRadius: this.panelRadiusLocal,
- avatarRadius: this.avatarRadiusLocal,
- avatarAltRadius: this.avatarAltRadiusLocal,
- tooltipRadius: this.tooltipRadiusLocal,
- attachmentRadius: this.attachmentRadiusLocal
- }})
- }
- },
-
- normalizeLocalState (colors, radii) {
- this.bgColorLocal = rgbstr2hex(colors.bg)
- this.btnColorLocal = rgbstr2hex(colors.btn)
- this.textColorLocal = rgbstr2hex(colors.fg)
- this.linkColorLocal = rgbstr2hex(colors.link)
-
- this.redColorLocal = rgbstr2hex(colors.cRed)
- this.blueColorLocal = rgbstr2hex(colors.cBlue)
- this.greenColorLocal = rgbstr2hex(colors.cGreen)
- this.orangeColorLocal = rgbstr2hex(colors.cOrange)
-
- this.btnRadiusLocal = radii.btnRadius || 4
- this.inputRadiusLocal = radii.inputRadius || 4
- this.panelRadiusLocal = radii.panelRadius || 10
- this.avatarRadiusLocal = radii.avatarRadius || 5
- this.avatarAltRadiusLocal = radii.avatarAltRadius || 50
- this.tooltipRadiusLocal = radii.tooltipRadius || 2
- this.attachmentRadiusLocal = radii.attachmentRadius || 5
+ this.$store.dispatch('setOption', {
+ name: 'customTheme',
+ value: {
+ shadows: this.shadowsLocal,
+ fonts: this.fontsLocal,
+ opacity: this.currentOpacity,
+ colors: this.currentColors,
+ radii: this.currentRadii
+ }
+ })
+ },
+
+ clearAll () {
+ this.normalizeLocalState(this.$store.state.config.customTheme)
+ },
+
+ // Clears all the extra stuff when loading V1 theme
+ clearV1 () {
+ Object.keys(this.$data)
+ .filter(_ => _.endsWith('ColorLocal') || _.endsWith('OpacityLocal'))
+ .filter(_ => !v1OnlyNames.includes(_))
+ .forEach(key => {
+ set(this.$data, key, undefined)
+ })
+ },
+
+ clearRoundness () {
+ Object.keys(this.$data)
+ .filter(_ => _.endsWith('RadiusLocal'))
+ .forEach(key => {
+ set(this.$data, key, undefined)
+ })
+ },
+
+ clearOpacity () {
+ Object.keys(this.$data)
+ .filter(_ => _.endsWith('OpacityLocal'))
+ .forEach(key => {
+ set(this.$data, key, undefined)
+ })
+ },
+
+ clearShadows () {
+ this.shadowsLocal = {}
+ },
+
+ clearFonts () {
+ this.fontsLocal = {}
+ },
+
+ /**
+ * This applies stored theme data onto form.
+ * @param {Object} input - input data
+ * @param {Number} version - version of data. 0 means try to guess based on data.
+ */
+ normalizeLocalState (input, version = 0) {
+ const colors = input.colors || input
+ const radii = input.radii || input
+ const opacity = input.opacity
+ const shadows = input.shadows || {}
+ const fonts = input.fonts || {}
+
+ if (version === 0) {
+ if (input.version) version = input.version
+ // Old v1 naming: fg is text, btn is foreground
+ if (typeof colors.text === 'undefined' && typeof colors.fg !== 'undefined') {
+ version = 1
+ }
+ // New v2 naming: text is text, fg is foreground
+ if (typeof colors.text !== 'undefined' && typeof colors.fg !== 'undefined') {
+ version = 2
+ }
+ }
+
+ // Stuff that differs between V1 and V2
+ if (version === 1) {
+ this.fgColorLocal = rgb2hex(colors.btn)
+ this.textColorLocal = rgb2hex(colors.fg)
+ }
+
+ const keys = new Set(version !== 1 ? Object.keys(colors) : [])
+ if (version === 1) {
+ // V1 ignores the rest
+ this.clearV1()
+ keys
+ .add('bg')
+ .add('link')
+ .add('cRed')
+ .add('cBlue')
+ .add('cGreen')
+ .add('cOrange')
+ }
+ keys.forEach(key => {
+ this[key + 'ColorLocal'] = rgb2hex(colors[key])
+ })
+
+ if (!this.keepRoundness) {
+ this.clearRoundness()
+ Object.entries(radii).forEach(([k, v]) => {
+ // 'Radius' is kept mostly for v1->v2 localstorage transition
+ const key = k.endsWith('Radius') ? k.split('Radius')[0] : k
+ this[key + 'RadiusLocal'] = v
+ })
+ }
+
+ if (!this.keepShadows) {
+ this.clearShadows()
+ this.shadowsLocal = shadows
+ this.shadowSelected = this.shadowsAvailable[0]
+ }
+
+ if (!this.keepFonts) {
+ 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: {
+ currentRadii () {
+ try {
+ this.previewRadii = generateRadii({ radii: this.currentRadii })
+ this.radiiInvalid = false
+ } catch (e) {
+ this.radiiInvalid = true
+ console.warn(e)
+ }
+ },
+ shadowsLocal: {
+ handler () {
+ try {
+ this.previewShadows = generateShadows({ shadows: this.shadowsLocal })
+ this.shadowsInvalid = false
+ } catch (e) {
+ this.shadowsInvalid = true
+ console.warn(e)
+ }
+ },
+ deep: true
+ },
+ fontsLocal: {
+ handler () {
+ try {
+ this.previewFonts = generateFonts({ fonts: this.fontsLocal })
+ this.fontsInvalid = false
+ } catch (e) {
+ this.fontsInvalid = true
+ console.warn(e)
+ }
+ },
+ deep: true
+ },
+ currentColors () {
+ try {
+ this.previewColors = generateColors({
+ opacity: this.currentOpacity,
+ colors: this.currentColors
+ })
+ this.colorsInvalid = false
+ } catch (e) {
+ this.colorsInvalid = true
+ console.warn(e)
+ }
+ },
+ currentOpacity () {
+ try {
+ this.previewColors = generateColors({
+ opacity: this.currentOpacity,
+ colors: this.currentColors
+ })
+ } catch (e) {
+ console.warn(e)
+ }
+ },
selected () {
- this.bgColorLocal = this.selected[1]
- this.btnColorLocal = this.selected[2]
- this.textColorLocal = this.selected[3]
- this.linkColorLocal = this.selected[4]
- this.redColorLocal = this.selected[5]
- this.greenColorLocal = this.selected[6]
- this.blueColorLocal = this.selected[7]
- this.orangeColorLocal = this.selected[8]
+ if (this.selectedVersion === 1) {
+ if (!this.keepRoundness) {
+ this.clearRoundness()
+ }
+
+ if (!this.keepShadows) {
+ this.clearShadows()
+ }
+
+ if (!this.keepOpacity) {
+ this.clearOpacity()
+ }
+
+ this.clearV1()
+
+ this.bgColorLocal = this.selected[1]
+ this.fgColorLocal = this.selected[2]
+ this.textColorLocal = this.selected[3]
+ this.linkColorLocal = this.selected[4]
+ this.cRedColorLocal = this.selected[5]
+ this.cGreenColorLocal = this.selected[6]
+ this.cBlueColorLocal = this.selected[7]
+ this.cOrangeColorLocal = this.selected[8]
+ } else if (this.selectedVersion >= 2) {
+ this.normalizeLocalState(this.selected.theme, 2)
+ }
}
}
}
diff --git a/src/components/style_switcher/style_switcher.scss b/src/components/style_switcher/style_switcher.scss
new file mode 100644
index 00000000..2c33224b
--- /dev/null
+++ b/src/components/style_switcher/style_switcher.scss
@@ -0,0 +1,275 @@
+@import '../../_variables.scss';
+.style-switcher {
+ .preset-switcher {
+ margin-right: 1em;
+ }
+
+ .style-control {
+ display: flex;
+ align-items: baseline;
+ margin-bottom: 5px;
+
+ .label {
+ flex: 1;
+ }
+
+ &.disabled {
+ input, select {
+ &:not(.exclude-disabled) {
+ opacity: .5
+ }
+ }
+ }
+
+ 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;
+ }
+
+ &[type=range] {
+ flex: 1;
+ min-width: 3em;
+ }
+
+ &[type=checkbox] + label {
+ margin: 6px 0;
+ }
+
+ &:not([type=number]):not([type=text]) {
+ align-self: center;
+ }
+ }
+ }
+
+ .import-warning {
+ color: $fallback--cRed;
+ color: var(--cRed, $fallback--cRed);
+ }
+
+ .tab-switcher {
+ margin: 0 -1em;
+ }
+
+ .reset-container {
+ flex-wrap: wrap;
+ }
+
+ .fonts-container,
+ .reset-container,
+ .apply-container,
+ .radius-container,
+ .color-container,
+ {
+ display: flex;
+ }
+
+ .fonts-container,
+ .radius-container {
+ flex-direction: column;
+ }
+
+ .color-container{
+ > h4 {
+ width: 99%;
+ }
+ flex-wrap: wrap;
+ justify-content: space-between;
+ }
+
+ .fonts-container,
+ .color-container,
+ .shadow-container,
+ .radius-container,
+ .presets-container {
+ margin: 1em 1em 0;
+ }
+
+ .tab-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ width: 100%;
+ min-height: 30px;
+
+ .btn {
+ min-width: 1px;
+ flex: 0 auto;
+ padding: 0 1em;
+ }
+
+ p {
+ flex: 1;
+ margin: 0;
+ }
+
+ margin-bottom: 1em;
+ }
+
+ .shadow-selector {
+ .override {
+ flex: 1;
+ margin-left: .5em;
+ }
+ .select-container {
+ margin-top: -4px;
+ margin-bottom: -3px;
+ }
+ }
+
+ .save-load,
+ .save-load-options {
+ display: flex;
+ justify-content: center;
+ align-items: baseline;
+
+ .import-export {
+ display: flex;
+ }
+
+ .override {
+ margin-left: .5em;
+ }
+ }
+
+ .save-load-options {
+ flex-wrap: wrap;
+ margin-top: .5em;
+ span {
+ margin: 0 .5em;
+ }
+ }
+
+ .preview-container {
+ border-top: 1px dashed;
+ border-bottom: 1px dashed;
+ border-color: $fallback--border;
+ border-color: var(--border, $fallback--border);
+ margin: 1em -1em 0;
+ padding: 1em;
+ background: var(--body-background-image);
+ background-size: cover;
+ background-position: 50% 50%;
+
+ .separator {
+ margin: 1em;
+ border-bottom: 1px solid;
+ border-color: $fallback--border;
+ border-color: var(--border, $fallback--border);
+ }
+
+ .panel-heading {
+ .badge, .alert, .btn, .faint {
+ margin-left: 1em;
+ }
+ .flex-spacer {
+ flex: 1;
+ }
+ }
+ .checkbox {
+ display: inline-flex;
+ align-items: baseline;
+ margin-right: 1em;
+ }
+
+ .btn {
+ margin-left: 0;
+ padding: 0 1em;
+ min-width: 3em;
+ min-height: 30px;
+ }
+ }
+
+ .apply-container {
+ justify-content: center;
+ }
+
+ .radius-item,
+ .color-item {
+ min-width: 20em;
+ margin: 5px 6px 0 0;
+ display:flex;
+ flex-direction: column;
+ flex: 1 1 0;
+
+ &.wide {
+ min-width: 60%
+ }
+
+ &:not(.wide):nth-child(2n+1) {
+ margin-right: 7px;
+
+ }
+
+ .color, .opacity {
+ display:flex;
+ align-items: baseline;
+ }
+ }
+
+ .radius-item {
+ flex-basis: auto;
+ }
+
+ .theme-radius-rn,
+ .theme-color-cl {
+ border: 0;
+ box-shadow: none;
+ background: transparent;
+ color: var(--faint, $fallback--faint);
+ align-self: stretch;
+ }
+
+ .theme-color-cl,
+ .theme-radius-in,
+ .theme-color-in {
+ margin-left: 4px;
+ }
+
+ .theme-radius-in {
+ min-width: 1em;
+ }
+
+ .theme-radius-in {
+ max-width: 7em;
+ flex: 1;
+ }
+
+ .theme-radius-lb{
+ max-width: 50em;
+ }
+
+ .theme-preview-content {
+ padding: 20px;
+ }
+
+ .btn {
+ margin-left: .25em;
+ margin-right: .25em;
+ }
+
+ .dummy {
+ .avatar {
+ background: linear-gradient(135deg, #b8e1fc 0%,#a9d2f3 10%,#90bae4 25%,#90bcea 37%,#90bff0 50%,#6ba8e5 51%,#a2daf5 83%,#bdf3fd 100%);
+ color: black;
+ text-align: center;
+ height: 48px;
+ line-height: 48px;
+ width: 48px;
+ float: left;
+ margin-right: 1em;
+ }
+ }
+}
diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue
index 72a338bd..2a7756ed 100644
--- a/src/components/style_switcher/style_switcher.vue
+++ b/src/components/style_switcher/style_switcher.vue
@@ -1,300 +1,307 @@
<template>
-<div>
+<div class="style-switcher">
<div class="presets-container">
- <div>
- {{$t('settings.presets')}}
- <label for="style-switcher" class='select'>
- <select id="style-switcher" v-model="selected" class="style-switcher">
- <option v-for="style in availableStyles"
- :value="style"
- :style="{
- backgroundColor: style[1],
- color: style[3]
- }">
- {{style[0]}}
- </option>
- </select>
- <i class="icon-down-open"/>
- </label>
+ <div class="save-load">
+ <div>
+ {{$t('settings.presets')}}
+ <label for="preset-switcher" class='select'>
+ <select id="preset-switcher" v-model="selected" class="preset-switcher">
+ <option v-for="style in availableStyles"
+ :value="style"
+ :style="{
+ backgroundColor: style[1] || style.theme.colors.bg,
+ color: style[3] || style.theme.colors.text
+ }">
+ {{style[0] || style.name}}
+ </option>
+ </select>
+ <i class="icon-down-open"/>
+ </label>
+ </div>
+ <div class="import-export">
+ <button class="btn" @click="exportCurrentTheme">{{ $t('settings.export_theme') }}</button>
+ <button class="btn" @click="importTheme">{{ $t('settings.import_theme') }}</button>
+ <p v-if="invalidThemeImported" class="import-warning">{{ $t('settings.invalid_theme_imported') }}</p>
+ </div>
</div>
- <div class="import-export">
- <button class="btn" @click="exportCurrentTheme">{{ $t('settings.export_theme') }}</button>
- <button class="btn" @click="importTheme">{{ $t('settings.import_theme') }}</button>
- <p v-if="invalidThemeImported" class="import-warning">{{ $t('settings.invalid_theme_imported') }}</p>
+ <div class="save-load-options">
+ <span>
+ <input
+ id="keep-shadows"
+ type="checkbox"
+ v-model="keepShadows">
+ <label for="keep-shadows">{{$t('settings.style.switcher.keep_shadows')}}</label>
+ </span>
+ <span>
+ <input
+ id="keep-opacity"
+ type="checkbox"
+ v-model="keepOpacity">
+ <label for="keep-opacity">{{$t('settings.style.switcher.keep_opacity')}}</label>
+ </span>
+ <span>
+ <input
+ id="keep-roundness"
+ type="checkbox"
+ v-model="keepRoundness">
+ <label for="keep-roundness">{{$t('settings.style.switcher.keep_roundness')}}</label>
+ </span>
+ <span>
+ <input
+ id="keep-fonts"
+ type="checkbox"
+ v-model="keepFonts">
+ <label for="keep-fonts">{{$t('settings.style.switcher.keep_fonts')}}</label>
+ </span>
+ <p>{{$t('settings.style.switcher.save_load_hint')}}</p>
</div>
</div>
<div class="preview-container">
- <div :style="{
- '--btnRadius': btnRadiusLocal + 'px',
- '--inputRadius': inputRadiusLocal + 'px',
- '--panelRadius': panelRadiusLocal + 'px',
- '--avatarRadius': avatarRadiusLocal + 'px',
- '--avatarAltRadius': avatarAltRadiusLocal + 'px',
- '--tooltipRadius': tooltipRadiusLocal + 'px',
- '--attachmentRadius': attachmentRadiusLocal + 'px'
- }">
- <div class="panel dummy">
- <div class="panel-heading" :style="{ 'background-color': btnColorLocal, 'color': textColorLocal }">Preview</div>
- <div class="panel-body theme-preview-content" :style="{ 'background-color': bgColorLocal, 'color': textColorLocal }">
- <div class="avatar" :style="{
- 'border-radius': avatarRadiusLocal + 'px'
- }">
- ( ͡° ͜ʖ ͡°)
- </div>
- <h4>Content</h4>
- <br>
- A bunch of more content and
- <a :style="{ color: linkColorLocal }">a nice lil' link</a>
- <i :style="{ color: blueColorLocal }" class="icon-reply"/>
- <i :style="{ color: greenColorLocal }" class="icon-retweet"/>
- <i :style="{ color: redColorLocal }" class="icon-cancel"/>
- <i :style="{ color: orangeColorLocal }" class="icon-star"/>
- <br>
- <button class="btn" :style="{ 'background-color': btnColorLocal, 'color': textColorLocal }">Button</button>
- </div>
+ <div class="panel dummy" :style="previewRules">
+ <div class="panel-heading">
+ {{$t('settings.style.preview.header')}}
+ <span class="badge badge-notification">
+ 99
+ </span>
+ <span class="alert error">
+ {{$t('settings.style.preview.error')}}
+ </span>
+ <button class="btn">
+ {{$t('settings.style.preview.button')}}
+ </button>
+ <span class="flex-spacer"/>
+ <span class="faint">
+ {{$t('settings.style.preview.header_faint')}}
+ </span>
</div>
- </div>
- </div>
-
- <div class="color-container">
- <p>{{$t('settings.theme_help')}}</p>
- <div class="color-item">
- <label for="bgcolor" class="theme-color-lb">{{$t('settings.background')}}</label>
- <input id="bgcolor" class="theme-color-cl" type="color" v-model="bgColorLocal">
- <input id="bgcolor-t" class="theme-color-in" type="text" v-model="bgColorLocal">
- </div>
- <div class="color-item">
- <label for="fgcolor" class="theme-color-lb">{{$t('settings.foreground')}}</label>
- <input id="fgcolor" class="theme-color-cl" type="color" v-model="btnColorLocal">
- <input id="fgcolor-t" class="theme-color-in" type="text" v-model="btnColorLocal">
- </div>
- <div class="color-item">
- <label for="textcolor" class="theme-color-lb">{{$t('settings.text')}}</label>
- <input id="textcolor" class="theme-color-cl" type="color" v-model="textColorLocal">
- <input id="textcolor-t" class="theme-color-in" type="text" v-model="textColorLocal">
- </div>
- <div class="color-item">
- <label for="linkcolor" class="theme-color-lb">{{$t('settings.links')}}</label>
- <input id="linkcolor" class="theme-color-cl" type="color" v-model="linkColorLocal">
- <input id="linkcolor-t" class="theme-color-in" type="text" v-model="linkColorLocal">
- </div>
- <div class="color-item">
- <label for="redcolor" class="theme-color-lb">{{$t('settings.cRed')}}</label>
- <input id="redcolor" class="theme-color-cl" type="color" v-model="redColorLocal">
- <input id="redcolor-t" class="theme-color-in" type="text" v-model="redColorLocal">
- </div>
- <div class="color-item">
- <label for="bluecolor" class="theme-color-lb">{{$t('settings.cBlue')}}</label>
- <input id="bluecolor" class="theme-color-cl" type="color" v-model="blueColorLocal">
- <input id="bluecolor-t" class="theme-color-in" type="text" v-model="blueColorLocal">
- </div>
- <div class="color-item">
- <label for="greencolor" class="theme-color-lb">{{$t('settings.cGreen')}}</label>
- <input id="greencolor" class="theme-color-cl" type="color" v-model="greenColorLocal">
- <input id="greencolor-t" class="theme-color-in" type="green" v-model="greenColorLocal">
- </div>
- <div class="color-item">
- <label for="orangecolor" class="theme-color-lb">{{$t('settings.cOrange')}}</label>
- <input id="orangecolor" class="theme-color-cl" type="color" v-model="orangeColorLocal">
- <input id="orangecolor-t" class="theme-color-in" type="text" v-model="orangeColorLocal">
- </div>
- </div>
-
- <div class="radius-container">
- <p>{{$t('settings.radii_help')}}</p>
- <div class="radius-item">
- <label for="btnradius" class="theme-radius-lb">{{$t('settings.btnRadius')}}</label>
- <input id="btnradius" class="theme-radius-rn" type="range" v-model="btnRadiusLocal" max="16">
- <input id="btnradius-t" class="theme-radius-in" type="text" v-model="btnRadiusLocal">
- </div>
- <div class="radius-item">
- <label for="inputradius" class="theme-radius-lb">{{$t('settings.inputRadius')}}</label>
- <input id="inputradius" class="theme-radius-rn" type="range" v-model="inputRadiusLocal" max="16">
- <input id="inputradius-t" class="theme-radius-in" type="text" v-model="inputRadiusLocal">
- </div>
- <div class="radius-item">
- <label for="panelradius" class="theme-radius-lb">{{$t('settings.panelRadius')}}</label>
- <input id="panelradius" class="theme-radius-rn" type="range" v-model="panelRadiusLocal" max="50">
- <input id="panelradius-t" class="theme-radius-in" type="text" v-model="panelRadiusLocal">
- </div>
- <div class="radius-item">
- <label for="avatarradius" class="theme-radius-lb">{{$t('settings.avatarRadius')}}</label>
- <input id="avatarradius" class="theme-radius-rn" type="range" v-model="avatarRadiusLocal" max="28">
- <input id="avatarradius-t" class="theme-radius-in" type="green" v-model="avatarRadiusLocal">
- </div>
- <div class="radius-item">
- <label for="avataraltradius" class="theme-radius-lb">{{$t('settings.avatarAltRadius')}}</label>
- <input id="avataraltradius" class="theme-radius-rn" type="range" v-model="avatarAltRadiusLocal" max="28">
- <input id="avataraltradius-t" class="theme-radius-in" type="text" v-model="avatarAltRadiusLocal">
- </div>
- <div class="radius-item">
- <label for="attachmentradius" class="theme-radius-lb">{{$t('settings.attachmentRadius')}}</label>
- <input id="attachmentrradius" class="theme-radius-rn" type="range" v-model="attachmentRadiusLocal" max="50">
- <input id="attachmentradius-t" class="theme-radius-in" type="text" v-model="attachmentRadiusLocal">
- </div>
- <div class="radius-item">
- <label for="tooltipradius" class="theme-radius-lb">{{$t('settings.tooltipRadius')}}</label>
- <input id="tooltipradius" class="theme-radius-rn" type="range" v-model="tooltipRadiusLocal" max="20">
- <input id="tooltipradius-t" class="theme-radius-in" type="text" v-model="tooltipRadiusLocal">
- </div>
- </div>
-
- <div class="apply-container">
- <button class="btn submit" @click="setCustomTheme">{{$t('general.apply')}}</button>
- </div>
-</div>
-</template>
-
-<script src="./style_switcher.js"></script>
-
-<style lang="scss">
-@import '../../_variables.scss';
-.style-switcher {
- margin-right: 1em;
-}
-
-.import-warning {
- color: $fallback--cRed;
- color: var(--cRed, $fallback--cRed);
-}
-
-.apply-container,
-.radius-container,
-.color-container,
-.presets-container {
- display: flex;
-
- p {
- flex: 2 0 100%;
- margin-top: 2em;
- margin-bottom: .5em;
- }
-}
-
-.radius-container {
- flex-direction: column;
-}
-
-.color-container {
- flex-wrap: wrap;
- justify-content: space-between;
-}
-
-.presets-container {
- justify-content: center;
- .import-export {
- display: flex;
-
- .btn {
- margin-left: .5em;
- }
- }
-}
-
-.preview-container {
- border-top: 1px dashed;
- border-bottom: 1px dashed;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
- margin: 1em -1em 0;
- padding: 1em;
-
- .btn {
- margin-top: 1em;
- min-height: 30px;
- width: 10em;
- }
-}
-
-.apply-container {
- justify-content: center;
-}
+ <div class="panel-body theme-preview-content">
+ <div class="avatar">
+ ( ͡° ͜ʖ ͡°)
+ </div>
+ <h4>Content</h4>
-.radius-item,
-.color-item {
- min-width: 20em;
- display:flex;
- flex: 1 1 0;
- align-items: baseline;
- margin: 5px 6px 5px 0;
+ <br>
- label {
- color: var(--faint, $fallback--faint);
- }
-}
+ <i18n path="settings.style.preview.text">
+ <a style="color: var(--link)">
+ {{$t('settings.style.preview.link')}}
+ </a>
+ </i18n>
-.radius-item {
- flex-basis: auto;
-}
+ <i style="color: var(--cBlue)" class="icon-reply"/>
+ <i style="color: var(--cGreen)" class="icon-retweet"/>
+ <i style="color: var(--cRed)" class="icon-cancel"/>
+ <i style="color: var(--cOrange)" class="icon-star"/>
-.theme-radius-rn,
-.theme-color-cl {
- border: 0;
- box-shadow: none;
- background: transparent;
- color: var(--faint, $fallback--faint);
- align-self: stretch;
-}
+ <br>
+ <br>
-.theme-color-cl,
-.theme-radius-in,
-.theme-color-in {
- margin-left: 4px;
-}
+ <input :value="$t('settings.style.preview.error')" type="text">
+ <span class="alert error">
+ {{$t('settings.style.preview.error')}}
+ </span>
-.theme-color-in {
- min-width: 4em;
-}
+ <br>
+ <br>
-.theme-radius-in {
- min-width: 1em;
-}
+ <span class="checkbox">
+ <input checked="very yes" type="checkbox" id="preview_checkbox">
+ <label for="preview_checkbox">{{$t('settings.style.preview.checkbox')}}</label>
+ </span>
+ <button class="btn">
+ {{$t('settings.style.preview.button')}}
+ </button>
-.theme-radius-in,
-.theme-color-in {
- max-width: 7em;
- flex: 1;
-}
+ <div class="separator"></div>
-.theme-radius-lb,
-.theme-color-lb {
- flex: 2;
- min-width: 7em;
-}
+ <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>
-.theme-radius-lb{
- max-width: 50em;
-}
+ <keep-alive>
+ <tab-switcher key="style-tweak">
+ <div :label="$t('settings.style.common_colors._tab_label')" class="color-container">
+ <div class="tab-header">
+ <p>{{$t('settings.theme_help')}}</p>
+ <button class="btn" @click="clearOpacity">{{$t('settings.style.switcher.clear_opacity')}}</button>
+ <button class="btn" @click="clearV1">{{$t('settings.style.switcher.clear_all')}}</button>
+ </div>
+ <p>{{$t('settings.theme_help_v2_1')}}</p>
+ <h4>{{ $t('settings.style.common_colors.main') }}</h4>
+ <div class="color-item">
+ <ColorInput name="bgColor" v-model="bgColorLocal" :label="$t('settings.background')"/>
+ <OpacityInput name="bgOpacity" v-model="bgOpacityLocal" :fallback="previewTheme.opacity.bg || 1"/>
+ <ColorInput name="textColor" v-model="textColorLocal" :label="$t('settings.text')"/>
+ <ContrastRatio :contrast="previewContrast.bgText"/>
+ <ColorInput name="linkColor" v-model="linkColorLocal" :label="$t('settings.links')"/>
+ <ContrastRatio :contrast="previewContrast.bgLink"/>
+ </div>
+ <div class="color-item">
+ <ColorInput name="fgColor" v-model="fgColorLocal" :label="$t('settings.foreground')"/>
+ <ColorInput name="fgTextColor" v-model="fgTextColorLocal" :label="$t('settings.text')" :fallback="previewTheme.colors.fgText"/>
+ <ColorInput name="fgLinkColor" v-model="fgLinkColorLocal" :label="$t('settings.links')" :fallback="previewTheme.colors.fgLink"/>
+ <p>{{ $t('settings.style.common_colors.foreground_hint') }}</p>
+ </div>
+ <h4>{{ $t('settings.style.common_colors.rgbo') }}</h4>
+ <div class="color-item">
+ <ColorInput name="cRedColor" v-model="cRedColorLocal" :label="$t('settings.cRed')"/>
+ <ContrastRatio :contrast="previewContrast.bgRed"/>
+ <ColorInput name="cBlueColor" v-model="cBlueColorLocal" :label="$t('settings.cBlue')"/>
+ <ContrastRatio :contrast="previewContrast.bgBlue"/>
+ </div>
+ <div class="color-item">
+ <ColorInput name="cGreenColor" v-model="cGreenColorLocal" :label="$t('settings.cGreen')"/>
+ <ContrastRatio :contrast="previewContrast.bgGreen"/>
+ <ColorInput name="cOrangeColor" v-model="cOrangeColorLocal" :label="$t('settings.cOrange')"/>
+ <ContrastRatio :contrast="previewContrast.bgOrange"/>
+ </div>
+ <p>{{$t('settings.theme_help_v2_2')}}</p>
+ </div>
-.theme-color-lb {
- max-width: 10em;
-}
+ <div :label="$t('settings.style.advanced_colors._tab_label')" class="color-container">
+ <div class="tab-header">
+ <p>{{$t('settings.theme_help')}}</p>
+ <button class="btn" @click="clearOpacity">{{$t('settings.style.switcher.clear_opacity')}}</button>
+ <button class="btn" @click="clearV1">{{$t('settings.style.switcher.clear_all')}}</button>
+ </div>
+ <div class="color-item">
+ <h4>{{ $t('settings.style.advanced_colors.alert') }}</h4>
+ <ColorInput name="alertError" v-model="alertErrorColorLocal" :label="$t('settings.style.advanced_colors.alert_error')" :fallback="previewTheme.colors.alertError"/>
+ <ContrastRatio :contrast="previewContrast.alertError"/>
+ </div>
+ <div class="color-item">
+ <h4>{{ $t('settings.style.advanced_colors.badge') }}</h4>
+ <ColorInput name="badgeNotification" v-model="badgeNotificationColorLocal" :label="$t('settings.style.advanced_colors.badge_notification')" :fallback="previewTheme.colors.badgeNotification"/>
+ </div>
+ <div class="color-item">
+ <h4>{{ $t('settings.style.advanced_colors.panel_header') }}</h4>
+ <ColorInput name="panelColor" v-model="panelColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/>
+ <OpacityInput name="panelOpacity" v-model="panelOpacityLocal" :fallback="previewTheme.opacity.panel || 1"/>
+ <ColorInput name="panelTextColor" v-model="panelTextColorLocal" :fallback="previewTheme.colors.panelText" :label="$t('settings.links')"/>
+ <ContrastRatio :contrast="previewContrast.panelText" large="1"/>
+ </div>
+ <div class="color-item">
+ <h4>{{ $t('settings.style.advanced_colors.top_bar') }}</h4>
+ <ColorInput name="topBarColor" v-model="topBarColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/>
+ <ColorInput name="topBarTextColor" v-model="topBarTextColorLocal" :fallback="previewTheme.colors.topBarText" :label="$t('settings.text')"/>
+ <ContrastRatio :contrast="previewContrast.topBarText"/>
+ <ColorInput name="topBarLinkColor" v-model="topBarLinkColorLocal" :fallback="previewTheme.colors.topBarLink" :label="$t('settings.links')"/>
+ <ContrastRatio :contrast="previewContrast.topBarLink"/>
+ </div>
+ <div class="color-item">
+ <h4>{{ $t('settings.style.advanced_colors.inputs') }}</h4>
+ <ColorInput name="inputColor" v-model="inputColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/>
+ <OpacityInput name="inputOpacity" v-model="inputOpacityLocal" :fallback="previewTheme.opacity.input || 1"/>
+ <ColorInput name="inputTextColor" v-model="inputTextColorLocal" :fallback="previewTheme.colors.inputText" :label="$t('settings.text')"/>
+ <ContrastRatio :contrast="previewContrast.inputText"/>
+ </div>
+ <div class="color-item">
+ <h4>{{ $t('settings.style.advanced_colors.buttons') }}</h4>
+ <ColorInput name="btnColor" v-model="btnColorLocal" :fallback="fgColorLocal" :label="$t('settings.background')"/>
+ <OpacityInput name="btnOpacity" v-model="btnOpacityLocal" :fallback="previewTheme.opacity.btn || 1"/>
+ <ColorInput name="btnTextColor" v-model="btnTextColorLocal" :fallback="previewTheme.colors.btnText" :label="$t('settings.text')"/>
+ <ContrastRatio :contrast="previewContrast.btnText"/>
+ </div>
+ <div class="color-item">
+ <h4>{{ $t('settings.style.advanced_colors.borders') }}</h4>
+ <ColorInput name="borderColor" v-model="borderColorLocal" :fallback="previewTheme.colors.border" label="Color"/>
+ <OpacityInput name="borderOpacity" v-model="borderOpacityLocal" :fallback="previewTheme.opacity.border || 1"/>
+ </div>
+ <div class="color-item">
+ <h4>{{ $t('settings.style.advanced_colors.faint_text') }}</h4>
+ <ColorInput name="faintColor" v-model="faintColorLocal" :fallback="previewTheme.colors.faint || 1" :label="$t('settings.text')"/>
+ <ColorInput name="faintLinkColor" v-model="faintLinkColorLocal" :fallback="previewTheme.colors.faintLink" :label="$t('settings.links')"/>
+ <ColorInput name="panelFaintColor" v-model="panelFaintColorLocal" :fallback="previewTheme.colors.panelFaint" :label="$t('settings.style.advanced_colors.panel_header')"/>
+ <OpacityInput name="faintOpacity" v-model="faintOpacityLocal" :fallback="previewTheme.opacity.faint || 0.5"/>
+ </div>
+ </div>
+ <div :label="$t('settings.style.radii._tab_label')" class="radius-container">
+ <div class="tab-header">
+ <p>{{$t('settings.radii_help')}}</p>
+ <button class="btn" @click="clearRoundness">{{$t('settings.style.switcher.clear_all')}}</button>
+ </div>
+ <RangeInput name="btnRadius" :label="$t('settings.btnRadius')" v-model="btnRadiusLocal" :fallback="previewTheme.radii.btn" max="16" hardMin="0"/>
+ <RangeInput name="inputRadius" :label="$t('settings.inputRadius')" v-model="inputRadiusLocal" :fallback="previewTheme.radii.input" max="9" hardMin="0"/>
+ <RangeInput name="checkboxRadius" :label="$t('settings.checkboxRadius')" v-model="checkboxRadiusLocal" :fallback="previewTheme.radii.checkbox" max="16" hardMin="0"/>
+ <RangeInput name="panelRadius" :label="$t('settings.panelRadius')" v-model="panelRadiusLocal" :fallback="previewTheme.radii.panel" max="50" hardMin="0"/>
+ <RangeInput name="avatarRadius" :label="$t('settings.avatarRadius')" v-model="avatarRadiusLocal" :fallback="previewTheme.radii.avatar" max="28" hardMin="0"/>
+ <RangeInput name="avatarAltRadius" :label="$t('settings.avatarAltRadius')" v-model="avatarAltRadiusLocal" :fallback="previewTheme.radii.avatarAlt" max="28" hardMin="0"/>
+ <RangeInput name="attachmentRadius" :label="$t('settings.attachmentRadius')" v-model="attachmentRadiusLocal" :fallback="previewTheme.radii.attachment" max="50" hardMin="0"/>
+ <RangeInput name="tooltipRadius" :label="$t('settings.tooltipRadius')" v-model="tooltipRadiusLocal" :fallback="previewTheme.radii.tooltip" max="50" hardMin="0"/>
+ </div>
+ <div :label="$t('settings.style.shadows._tab_label')" class="shadow-container">
+ <div class="tab-header shadow-selector">
+ <div class="select-container">
+ {{$t('settings.style.shadows.component')}}
+ <label for="shadow-switcher" class="select">
+ <select id="shadow-switcher" v-model="shadowSelected" class="shadow-switcher">
+ <option v-for="shadow in shadowsAvailable"
+ :value="shadow">
+ {{$t('settings.style.shadows.components.' + shadow)}}
+ </option>
+ </select>
+ <i class="icon-down-open"/>
+ </label>
+ </div>
+ <div class="override">
+ <label for="override" class="label">
+ {{$t('settings.style.shadows.override')}}
+ </label>
+ <input
+ v-model="currentShadowOverriden"
+ name="override"
+ id="override"
+ class="input-override"
+ type="checkbox">
+ <label class="checkbox-label" for="override"></label>
+ </div>
+ <button class="btn" @click="clearShadows">{{$t('settings.style.switcher.clear_all')}}</button>
+ </div>
+ <shadow-control :ready="!!currentShadowFallback" :fallback="currentShadowFallback" v-model="currentShadow"/>
+ </div>
+ <div :label="$t('settings.style.fonts._tab_label')" class="fonts-container">
+ <div class="tab-header">
+ <p>{{$t('settings.style.fonts.help')}}</p>
+ <button class="btn" @click="clearFonts">{{$t('settings.style.switcher.clear_all')}}</button>
+ </div>
+ <FontControl
+ name="ui"
+ v-model="fontsLocal.interface"
+ :label="$t('settings.style.fonts.components.interface')"
+ :fallback="previewTheme.fonts.interface"
+ no-inherit="1"/>
+ <FontControl
+ name="input"
+ v-model="fontsLocal.input"
+ :label="$t('settings.style.fonts.components.input')"
+ :fallback="previewTheme.fonts.input"/>
+ <FontControl
+ name="post"
+ v-model="fontsLocal.post"
+ :label="$t('settings.style.fonts.components.post')"
+ :fallback="previewTheme.fonts.post"/>
+ <FontControl
+ name="postCode"
+ v-model="fontsLocal.postCode"
+ :label="$t('settings.style.fonts.components.postCode')"
+ :fallback="previewTheme.fonts.postCode"/>
+ </div>
+ </tab-switcher>
+ </keep-alive>
-.theme-color-cl {
- padding: 1px;
- max-width: 8em;
- height: 100%;
- flex: 0;
- min-width: 2em;
- cursor: pointer;
- max-height: 29px;
-}
+ <div class="apply-container">
+ <button class="btn submit" :disabled="!themeValid" @click="setCustomTheme">{{$t('general.apply')}}</button>
+ <button class="btn" @click="clearAll">{{$t('settings.style.switcher.reset')}}</button>
+ </div>
+</div>
+</template>
-.theme-preview-content {
- padding: 20px;
-}
+<script src="./style_switcher.js"></script>
-.dummy {
- .avatar {
- background: linear-gradient(135deg, #b8e1fc 0%,#a9d2f3 10%,#90bae4 25%,#90bcea 37%,#90bff0 50%,#6ba8e5 51%,#a2daf5 83%,#bdf3fd 100%);
- color: black;
- text-align: center;
- height: 48px;
- line-height: 48px;
- width: 48px;
- float: left;
- margin-right: 1em;
- }
-}
-</style>
+<style src="./style_switcher.scss" lang="scss"></style>