diff options
Diffstat (limited to 'src/components/style_switcher')
| -rw-r--r-- | src/components/style_switcher/preview.vue | 78 | ||||
| -rw-r--r-- | src/components/style_switcher/style_switcher.js | 672 | ||||
| -rw-r--r-- | src/components/style_switcher/style_switcher.scss | 335 | ||||
| -rw-r--r-- | src/components/style_switcher/style_switcher.vue | 538 |
4 files changed, 1209 insertions, 414 deletions
diff --git a/src/components/style_switcher/preview.vue b/src/components/style_switcher/preview.vue new file mode 100644 index 00000000..09a136e9 --- /dev/null +++ b/src/components/style_switcher/preview.vue @@ -0,0 +1,78 @@ +<template> +<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> + <div class="panel-body theme-preview-content"> + <div class="post"> + <div class="avatar"> + ( ͡° ͜ʖ ͡°) + </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> + + <div class="icons"> + <i style="color: var(--cBlue)" class="icon-reply"/> + <i style="color: var(--cGreen)" class="icon-retweet"/> + <i style="color: var(--cOrange)" class="icon-star"/> + <i style="color: var(--cRed)" class="icon-cancel"/> + </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> + </div> + <div class="separator"></div> + + <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 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> + </div> + </div> +</div> +</template> diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js index 95c15b49..6a4e1cba 100644 --- a/src/components/style_switcher/style_switcher.js +++ b/src/components/style_switcher/style_switcher.js @@ -1,21 +1,101 @@ -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, getThemes } 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' +import Preview from './preview.vue' +import ExportImport from '../export_import/export_import.vue' + +// List of color values used in v1 +const v1OnlyNames = [ + 'bg', + 'fg', + 'text', + 'link', + 'cRed', + 'cGreen', + 'cBlue', + 'cOrange' +].map(_ => _ + 'ColorLocal') export default { data () { return { availableStyles: [], selected: this.$store.state.config.theme, - invalidThemeImported: false, - bgColorLocal: '', - btnColorLocal: '', + + previewShadows: {}, + previewColors: {}, + previewRadii: {}, + previewFonts: {}, + + shadowsInvalid: true, + colorsInvalid: true, + radiiInvalid: true, + + keepColor: false, + 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, + panelLinkColorLocal: 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: '', @@ -26,144 +106,470 @@ export default { created () { const self = this - window.fetch('/static/styles.json') - .then((data) => data.json()) - .then((themes) => { - self.availableStyles = themes - }) + getThemes().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] + } }, - methods: { - exportCurrentTheme () { - const stringified = JSON.stringify({ + 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, + 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, + 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), + 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), + 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) + } + + 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 + }, + exportedTheme () { + const saveEverything = ( + !this.keepFonts && + !this.keepShadows && + !this.keepOpacity && + !this.keepRoundness && + !this.keepColor + ) + + const theme = {} + + if (this.keepFonts || saveEverything) { + theme.fonts = this.fontsLocal + } + if (this.keepShadows || saveEverything) { + theme.shadows = this.shadowsLocal + } + if (this.keepOpacity || saveEverything) { + theme.opacity = this.currentOpacity + } + if (this.keepColor || saveEverything) { + theme.colors = this.currentColors + } + if (this.keepRoundness || saveEverything) { + theme.radii = this.currentRadii + } + + return { // 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 - }, null, 2) // Pretty-print and indent with 2 spaces - - // Create an invisible link with a data url and simulate a click - const e = document.createElement('a') - e.setAttribute('download', 'pleroma_theme.json') - e.setAttribute('href', 'data:application/json;base64,' + window.btoa(stringified)) - e.style.display = 'none' - - document.body.appendChild(e) - e.click() - document.body.removeChild(e) - }, - - importTheme () { - this.invalidThemeImported = false - const filePicker = document.createElement('input') - filePicker.setAttribute('type', 'file') - filePicker.setAttribute('accept', '.json') - - filePicker.addEventListener('change', event => { - if (event.target.files[0]) { - // eslint-disable-next-line no-undef - const reader = new FileReader() - reader.onload = ({target}) => { - try { - const parsed = JSON.parse(target.result) - if (parsed._pleroma_theme_version === 1) { - this.normalizeLocalState(parsed.colors, parsed.radii) - } else { - // A theme from the future, spooky - this.invalidThemeImported = true - } - } catch (e) { - // This will happen both if there is a JSON syntax error or the theme is missing components - this.invalidThemeImported = true - } - } - reader.readAsText(event.target.files[0]) + _pleroma_theme_version: 2, theme + } + } + }, + components: { + ColorInput, + OpacityInput, + RangeInput, + ContrastRatio, + ShadowControl, + FontControl, + TabSwitcher, + Preview, + ExportImport + }, + methods: { + setCustomTheme () { + this.$store.dispatch('setOption', { + name: 'customTheme', + value: { + shadows: this.shadowsLocal, + fonts: this.fontsLocal, + opacity: this.currentOpacity, + colors: this.currentColors, + radii: this.currentRadii } }) + }, + onImport (parsed) { + if (parsed._pleroma_theme_version === 1) { + this.normalizeLocalState(parsed, 1) + } else if (parsed._pleroma_theme_version === 2) { + this.normalizeLocalState(parsed.theme, 2) + } + }, + importValidator (parsed) { + const version = parsed._pleroma_theme_version + return version >= 1 || version <= 2 + }, + clearAll () { + const state = this.$store.state.config.customTheme + const version = state.colors ? 2 : 'l1' + this.normalizeLocalState(this.$store.state.config.customTheme, version) + }, - document.body.appendChild(filePicker) - filePicker.click() - document.body.removeChild(filePicker) + // 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) + }) }, - 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 + 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. Supports three versions of data: + * 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 {Number} version - version of data. 0 means try to guess based on data. "l1" means v1, locastorage type + */ + 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) + } + + if (!this.keepColor) { + this.clearV1() + const keys = new Set(version !== 1 ? Object.keys(colors) : []) + if (version === 1 || version === 'l1') { + 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() + } + + if (!this.keepColor) { + 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..135c113a --- /dev/null +++ b/src/components/style_switcher/style_switcher.scss @@ -0,0 +1,335 @@ +@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: flex-start; + } + } + } + + .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-right: .5em; + } + + 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; + flex-wrap: wrap; + + .presets, + .import-export { + margin-bottom: .5em; + } + + .import-export { + display: flex; + } + + .override { + margin-left: .5em; + } + } + + .save-load-options { + flex-wrap: wrap; + margin-top: .5em; + justify-content: center; + .keep-option { + margin: 0 .5em .5em; + min-width: 25%; + } + } + + .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%; + + .dummy { + .post { + font-family: var(--postFont); + display: flex; + + .content { + flex: 1; + + h4 { + margin-bottom: .25em; + } + + .icons { + margin-top: .5em; + display: flex; + + i { + margin-right: 1em; + } + } + } + } + + .after-post { + margin-top: 1em; + display: flex; + align-items: center; + } + + .avatar, .avatar-alt{ + background: linear-gradient(135deg, #b8e1fc 0%,#a9d2f3 10%,#90bae4 25%,#90bcea 37%,#90bff0 50%,#6ba8e5 51%,#a2daf5 83%,#bdf3fd 100%); + color: black; + font-family: sans-serif; + text-align: center; + margin-right: 1em; + } + + .avatar-alt { + flex: 0 auto; + margin-left: 28px; + font-size: 12px; + min-width: 20px; + min-height: 20px; + line-height: 20px; + border-radius: $fallback--avatarAltRadius; + border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius); + } + + .avatar { + flex: 0 auto; + width: 48px; + height: 48px; + font-size: 14px; + line-height: 48px; + } + + .actions { + display: flex; + align-items: baseline; + + .checkbox { + display: inline-flex; + align-items: baseline; + margin-right: 1em; + flex: 1; + } + } + + .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; + white-space: nowrap; + } + .faint { + text-overflow: ellipsis; + min-width: 2em; + overflow-x: hidden; + } + .flex-spacer { + flex: 1; + } + } + .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; + } +} diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue index 72a338bd..84963c81 100644 --- a/src/components/style_switcher/style_switcher.vue +++ b/src/components/style_switcher/style_switcher.vue @@ -1,300 +1,276 @@ <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"> + <export-import + :exportObject='exportedTheme' + :exportLabel='$t("settings.export_theme")' + :importLabel='$t("settings.import_theme")' + :importFailedText='$t("settings.invalid_theme_imported")' + :onImport='onImport' + :validator='importValidator'> + <template slot="before"> + <div class="presets"> + {{$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> + </template> + </export-import> </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 class="keep-option"> + <input + id="keep-color" + type="checkbox" + v-model="keepColor"> + <label for="keep-color">{{$t('settings.style.switcher.keep_color')}}</label> + </span> + <span class="keep-option"> + <input + id="keep-shadows" + type="checkbox" + v-model="keepShadows"> + <label for="keep-shadows">{{$t('settings.style.switcher.keep_shadows')}}</label> + </span> + <span class="keep-option"> + <input + id="keep-opacity" + type="checkbox" + v-model="keepOpacity"> + <label for="keep-opacity">{{$t('settings.style.switcher.keep_opacity')}}</label> + </span> + <span class="keep-option"> + <input + id="keep-roundness" + type="checkbox" + v-model="keepRoundness"> + <label for="keep-roundness">{{$t('settings.style.switcher.keep_roundness')}}</label> + </span> + <span class="keep-option"> + <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> + <preview :style="previewRules"/> + </div> + + <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> - </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 :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.text')"/> + <ContrastRatio :contrast="previewContrast.panelText" large="1"/> + <ColorInput name="panelLinkColor" v-model="panelLinkColorLocal" :fallback="previewTheme.colors.panelLink" :label="$t('settings.links')"/> + <ContrastRatio :contrast="previewContrast.panelLink" 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="$t('settings.style.common.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 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 :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 v-if="shadowSelected === 'avatar' || shadowSelected === 'avatarStatus'"> + <i18n path="settings.style.shadows.filter_hint.always_drop_shadow" tag="p"> + <code>filter: drop-shadow()</code> + </i18n> + <p>{{$t('settings.style.shadows.filter_hint.avatar_inset')}}</p> + <i18n path="settings.style.shadows.filter_hint.drop_shadow_syntax" tag="p"> + <code>drop-shadow</code> + <code>spread-radius</code> + <code>inset</code> + </i18n> + <i18n path="settings.style.shadows.filter_hint.inset_classic" tag="p"> + <code>box-shadow</code> + </i18n> + <p>{{$t('settings.style.shadows.filter_hint.spread_zero')}}</p> + </div> + </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> <div class="apply-container"> - <button class="btn submit" @click="setCustomTheme">{{$t('general.apply')}}</button> + <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> <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; -} - -.radius-item, -.color-item { - min-width: 20em; - display:flex; - flex: 1 1 0; - align-items: baseline; - margin: 5px 6px 5px 0; - - label { - color: var(--faint, $fallback--faint); - } -} - -.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-color-in { - min-width: 4em; -} - -.theme-radius-in { - min-width: 1em; -} - -.theme-radius-in, -.theme-color-in { - max-width: 7em; - flex: 1; -} - -.theme-radius-lb, -.theme-color-lb { - flex: 2; - min-width: 7em; -} - -.theme-radius-lb{ - max-width: 50em; -} - -.theme-color-lb { - max-width: 10em; -} - -.theme-color-cl { - padding: 1px; - max-width: 8em; - height: 100%; - flex: 0; - min-width: 2em; - cursor: pointer; - max-height: 29px; -} - -.theme-preview-content { - padding: 20px; -} - -.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> |
