diff options
| author | Henry Jameson <me@hjkos.com> | 2018-12-13 17:50:36 +0300 |
|---|---|---|
| committer | Henry Jameson <me@hjkos.com> | 2018-12-13 17:50:36 +0300 |
| commit | 610724ffcd6bd22f496f45a955e80f7f2a051e1a (patch) | |
| tree | 0f48b0844429eae0bb6283c9df9c6d609bd7b828 /src/components/style_switcher/style_switcher.js | |
| parent | 403c86e4d1b0067634d1f0f6bd3d6908f8b4481b (diff) | |
| parent | dbe79a3c2673b9a14bd8f3b037eca5999dd6a018 (diff) | |
Merge remote-tracking branch 'upstream/develop' into mobile-back
* upstream/develop: (142 commits)
fix timeago font
added hide_network option, fixed properties naming
Fix fetching new users, add storing local users in usersObjects with their screen_name as well as id, so that they could be fetched zero-state with screen-name link.
improve notification subscription
Fix typo that prevented scope copy from working.
added check for activatePanel is function or not
addressed PR comments
activate panel on user screen click
added not preload check so hidden toggles asap
removed counters from left panel
added router-links to all relavent links
added activatePanel onclick for timeago button
added PR comments
add checkbox to disable web push
removed brackets from condition
resolved lint issue
renamed config to preload images and add ident to config
added config for preload and made attachment responsive to it
preload nsfw image
fix
...
Diffstat (limited to 'src/components/style_switcher/style_switcher.js')
| -rw-r--r-- | src/components/style_switcher/style_switcher.js | 672 |
1 files changed, 539 insertions, 133 deletions
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) + } } } } |
