diff --git a/package.json b/package.json index b716e7c6..1149d200 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "dependencies": { "babel-plugin-add-module-exports": "^0.2.1", "babel-plugin-lodash": "^3.2.11", + "chromatism": "^3.0.0", "diff": "^3.0.1", "karma-mocha-reporter": "^2.2.1", "localforage": "^1.5.0", diff --git a/src/App.scss b/src/App.scss index 056a235e..1119caf2 100644 --- a/src/App.scss +++ b/src/App.scss @@ -51,7 +51,7 @@ a { button { user-select: none; color: $fallback--fg; - color: var(--fg, $fallback--fg); + color: var(--btnText, $fallback--fg); background-color: $fallback--btn; background-color: var(--btn, $fallback--btn); border: none; @@ -254,7 +254,7 @@ nav { mask-position: center; mask-size: contain; background-color: $fallback--fg; - background-color: var(--fg, $fallback--fg); + background-color: var(--topBarText, $fallback--fg); position: absolute; top: 0; bottom: 0; @@ -330,8 +330,9 @@ main-router { padding: .6em .6em; text-align: left; line-height: 28px; + color: var(--panelText); background-color: $fallback--btn; - background-color: var(--btn, $fallback--btn); + background-color: var(--panel, $fallback--btn); align-items: baseline; .title { @@ -387,8 +388,9 @@ main-router { nav { z-index: 1000; + color: var(--topBarText); background-color: $fallback--btn; - background-color: var(--btn, $fallback--btn); + background-color: var(--topBar, $fallback--btn); color: $fallback--faint; color: var(--faint, $fallback--faint); box-shadow: 0px 0px 4px rgba(0,0,0,.6); diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue index 42c660a3..eebb2cb7 100644 --- a/src/components/settings/settings.vue +++ b/src/components/settings/settings.vue @@ -18,6 +18,7 @@
+
@@ -146,6 +147,7 @@
+
diff --git a/src/components/style_switcher/style_switcher.js b/src/components/style_switcher/style_switcher.js index 95c15b49..5f76c038 100644 --- a/src/components/style_switcher/style_switcher.js +++ b/src/components/style_switcher/style_switcher.js @@ -1,4 +1,7 @@ -import { rgbstr2hex } from '../../services/color_convert/color_convert.js' +import { rgb2hex } from '../../services/color_convert/color_convert.js' +import ColorInput from '../color_input/color_input.vue' +import OpacityInput from '../opacity_input/opacity_input.vue' +import StyleSetter from '../../services/style_setter/style_setter.js' export default { data () { @@ -7,13 +10,23 @@ export default { selected: this.$store.state.config.theme, invalidThemeImported: false, bgColorLocal: '', + bgOpacityLocal: 0, btnColorLocal: '', + btnOpacityLocal: '', + textColorLocal: '', linkColorLocal: '', + + panelColorLocal: undefined, + panelOpacityLocal: undefined, + topBarColorLocal: undefined, + topBarOpacityLocal: undefined, + redColorLocal: '', blueColorLocal: '', greenColorLocal: '', orangeColorLocal: '', + btnRadiusLocal: '', inputRadiusLocal: '', panelRadiusLocal: '', @@ -33,7 +46,48 @@ export default { }) }, mounted () { - this.normalizeLocalState(this.$store.state.config.colors, this.$store.state.config.radii) + this.normalizeLocalState(this.$store.state.config.customTheme) + }, + computed: { + currentTheme () { + return { + colors: { + bg: this.bgColorLocal, + fg: this.textColorLocal, + panel: this.panelColorLocal, + topBar: this.topBarColorLocal, + btn: this.btnColorLocal, + link: this.linkColorLocal, + cRed: this.redColorLocal, + cBlue: this.blueColorLocal, + cGreen: this.greenColorLocal, + cOrange: this.orangeColorLocal + }, + radii: { + btnRadius: this.btnRadiusLocal, + inputRadius: this.inputRadiusLocal, + panelRadius: this.panelRadiusLocal, + avatarRadius: this.avatarRadiusLocal, + avatarAltRadius: this.avatarAltRadiusLocal, + tooltipRadius: this.tooltipRadiusLocal, + attachmentRadius: this.attachmentRadiusLocal + } + } + }, + previewRules () { + try { + const generated = StyleSetter.generatePreset(this.currentTheme.colors) + return [generated.colorRules, generated.radiiRules].join(';') + } catch (e) { + console.error('CATCH') + console.error(e) + return '' + } + } + }, + components: { + ColorInput, + OpacityInput }, methods: { exportCurrentTheme () { @@ -101,57 +155,62 @@ export default { 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 - }}) - } + this.$store.dispatch('setOption', { + name: 'customTheme', + value: this.currentTheme + }) }, - normalizeLocalState (colors, radii) { - this.bgColorLocal = rgbstr2hex(colors.bg) - this.btnColorLocal = rgbstr2hex(colors.btn) - this.textColorLocal = rgbstr2hex(colors.fg) - this.linkColorLocal = rgbstr2hex(colors.link) + normalizeLocalState (input) { + const colors = input.colors || input + const radii = input.radii || input + let i = 0 + console.log('BENIS') + console.log(colors) - this.redColorLocal = rgbstr2hex(colors.cRed) - this.blueColorLocal = rgbstr2hex(colors.cBlue) - this.greenColorLocal = rgbstr2hex(colors.cGreen) - this.orangeColorLocal = rgbstr2hex(colors.cOrange) + console.log(i++) + this.bgColorLocal = rgb2hex(colors.bg) + console.log(i++) + this.btnColorLocal = rgb2hex(colors.btn) + console.log(i++) + this.textColorLocal = rgb2hex(colors.text || colors.fg) + console.log(i++) + this.linkColorLocal = rgb2hex(colors.link) + console.log(i++) + + this.panelColorLocal = colors.panel ? rgb2hex(colors.panel) : undefined + console.log(i++) + this.topBarColorLocal = colors.topBad ? rgb2hex(colors.topBar) : undefined + console.log(i++) + + this.redColorLocal = rgb2hex(colors.cRed) + console.log(i++) + console.log('red') + console.log(colors.cRed) + console.log(this.redColorLocal) + this.blueColorLocal = rgb2hex(colors.cBlue) + console.log(i++) + console.log('blue', this.blueColorLocal, colors.cBlue) + this.greenColorLocal = rgb2hex(colors.cGreen) + console.log(i++) + this.orangeColorLocal = rgb2hex(colors.cOrange) + console.log(i++) this.btnRadiusLocal = radii.btnRadius || 4 + console.log(i++) this.inputRadiusLocal = radii.inputRadius || 4 + console.log(i++) this.panelRadiusLocal = radii.panelRadius || 10 + console.log(i++) this.avatarRadiusLocal = radii.avatarRadius || 5 + console.log(i++) this.avatarAltRadiusLocal = radii.avatarAltRadius || 50 + console.log(i++) this.tooltipRadiusLocal = radii.tooltipRadius || 2 + console.log(i++) this.attachmentRadiusLocal = radii.attachmentRadius || 5 + console.log(i++) } }, watch: { diff --git a/src/components/style_switcher/style_switcher.vue b/src/components/style_switcher/style_switcher.vue index 72a338bd..339d7c3d 100644 --- a/src/components/style_switcher/style_switcher.vue +++ b/src/components/style_switcher/style_switcher.vue @@ -24,80 +24,73 @@ -
-
-
-
Preview
-
-
- ( ͡° ͜ʖ ͡°) -
-

Content

-
- A bunch of more content and - a nice lil' link - - - - -
- +
+
+
Preview
+
+
+ ( ͡° ͜ʖ ͡°)
+

Content

+
+ A bunch of more content and + a nice lil' link + + + + +
+

{{$t('settings.theme_help')}}

-
- - - +

Basic colors!!

+
+
+ + +
+
+ + +
+
+ +
+
+ +
-
- - - + +

More customs!

+
+
+ + +
+
+ + +
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
- - - + +

Rainbows!!!

+
+
+ +
+
+ +
+
+ +
+
+ +
@@ -161,7 +154,7 @@ .apply-container, .radius-container, -.color-container, +.color-container > div, .presets-container { display: flex; @@ -176,7 +169,7 @@ flex-direction: column; } -.color-container { +.color-container > div{ flex-wrap: wrap; justify-content: space-between; } @@ -214,14 +207,30 @@ .radius-item, .color-item { min-width: 20em; + margin: 5px 6px 0 0; display:flex; + flex-direction: column; flex: 1 1 0; - align-items: baseline; - margin: 5px 6px 5px 0; + + &:nth-child(2n+1) { + margin-right: 7px; + + } + + .color, .opacity { + display:flex; + align-items: baseline; + } label { color: var(--faint, $fallback--faint); } + .opacity-control { + margin-top: 5px; + height: 12px; + line-height: 12px; + font-size: 12px; + } } .radius-item { @@ -243,44 +252,19 @@ margin-left: 4px; } -.theme-color-in { - min-width: 4em; -} - .theme-radius-in { min-width: 1em; } -.theme-radius-in, -.theme-color-in { +.theme-radius-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; } diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js index 006107e2..e55b3b79 100644 --- a/src/lib/persisted_state.js +++ b/src/lib/persisted_state.js @@ -73,6 +73,7 @@ export default function createPersistedState ({ loaded = true } catch (e) { console.log("Couldn't load state") + console.error(e) loaded = true } }) diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js index 13dd8979..efb43327 100644 --- a/src/services/color_convert/color_convert.js +++ b/src/services/color_convert/color_convert.js @@ -1,6 +1,10 @@ import { map } from 'lodash' const rgb2hex = (r, g, b) => { + console.log(r) + if (typeof r === 'object') { + ({ r, g, b } = r) + } [r, g, b] = map([r, g, b], (val) => { val = Math.ceil(val) val = val < 0 ? 0 : val @@ -27,8 +31,16 @@ const rgbstr2hex = (rgb) => { return `#${((Number(rgb[0]) << 16) + (Number(rgb[1]) << 8) + Number(rgb[2])).toString(16)}` } +const mixrgb = (a, b) => { + return Object.keys(a).reduce((acc, k) => { + acc[k] = (a[k] + b[k]) / 2 + return acc + }, {}) +} + export { rgb2hex, hex2rgb, + mixrgb, rgbstr2hex } diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js index 493d444e..72782594 100644 --- a/src/services/style_setter/style_setter.js +++ b/src/services/style_setter/style_setter.js @@ -1,5 +1,6 @@ import { times } from 'lodash' -import { rgb2hex, hex2rgb } from '../color_convert/color_convert.js' +import { brightness, invertLightness, convert } from 'chromatism' +import { rgb2hex, hex2rgb, mixrgb } from '../color_convert/color_convert.js' // While this is not used anymore right now, I left it in if we want to do custom // styles that aren't just colors, so user can pick from a few different distinct @@ -53,7 +54,23 @@ const setStyle = (href, commit) => { cssEl.addEventListener('load', setDynamic) } -const setColors = (col, commit) => { +const rgb2rgba = function (rgba) { + return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})` +} + +const getTextColor = function (bg, text) { + const bgIsLight = convert(bg).hsl.l > 50 + const textIsLight = convert(text).hsl.l > 50 + + if ((bgIsLight && textIsLight) || (!bgIsLight && !textIsLight)) { + const base = typeof text.a !== 'undefined' ? { a: text.a } : {} + return Object.assign(base, invertLightness(text).rgb) + } + return text +} + +const setColors = (input, commit) => { + const { colorRules, radiiRules, col } = generatePreset(input) const head = document.head const body = document.body body.style.display = 'none' @@ -62,51 +79,83 @@ const setColors = (col, commit) => { head.appendChild(styleEl) const styleSheet = styleEl.sheet - const isDark = (col.text.r + col.text.g + col.text.b) > (col.bg.r + col.bg.g + col.bg.b) - let colors = {} - let radii = {} - - const mod = isDark ? -10 : 10 - - colors.bg = rgb2hex(col.bg.r, col.bg.g, col.bg.b) // background - colors.lightBg = rgb2hex((col.bg.r + col.fg.r) / 2, (col.bg.g + col.fg.g) / 2, (col.bg.b + col.fg.b) / 2) // hilighted bg - colors.btn = rgb2hex(col.fg.r, col.fg.g, col.fg.b) // panels & buttons - colors.input = `rgba(${col.fg.r}, ${col.fg.g}, ${col.fg.b}, .5)` - colors.border = rgb2hex(col.fg.r - mod, col.fg.g - mod, col.fg.b - mod) // borders - colors.faint = `rgba(${col.text.r}, ${col.text.g}, ${col.text.b}, .5)` - colors.fg = rgb2hex(col.text.r, col.text.g, col.text.b) // text - colors.lightFg = rgb2hex(col.text.r - mod * 5, col.text.g - mod * 5, col.text.b - mod * 5) // strong text - - colors['base07'] = rgb2hex(col.text.r - mod * 2, col.text.g - mod * 2, col.text.b - mod * 2) - - colors.link = rgb2hex(col.link.r, col.link.g, col.link.b) // links - colors.icon = rgb2hex((col.bg.r + col.text.r) / 2, (col.bg.g + col.text.g) / 2, (col.bg.b + col.text.b) / 2) // icons - - colors.cBlue = col.cBlue && rgb2hex(col.cBlue.r, col.cBlue.g, col.cBlue.b) - colors.cRed = col.cRed && rgb2hex(col.cRed.r, col.cRed.g, col.cRed.b) - colors.cGreen = col.cGreen && rgb2hex(col.cGreen.r, col.cGreen.g, col.cGreen.b) - colors.cOrange = col.cOrange && rgb2hex(col.cOrange.r, col.cOrange.g, col.cOrange.b) - - colors.cAlertRed = col.cRed && `rgba(${col.cRed.r}, ${col.cRed.g}, ${col.cRed.b}, .5)` - - radii.btnRadius = col.btnRadius - radii.inputRadius = col.inputRadius - radii.panelRadius = col.panelRadius - radii.avatarRadius = col.avatarRadius - radii.avatarAltRadius = col.avatarAltRadius - radii.tooltipRadius = col.tooltipRadius - radii.attachmentRadius = col.attachmentRadius - styleSheet.toString() - styleSheet.insertRule(`body { ${Object.entries(colors).filter(([k, v]) => v).map(([k, v]) => `--${k}: ${v}`).join(';')} }`, 'index-max') - styleSheet.insertRule(`body { ${Object.entries(radii).filter(([k, v]) => v).map(([k, v]) => `--${k}: ${v}px`).join(';')} }`, 'index-max') + styleSheet.insertRule(`body { ${colorRules} }`, 'index-max') + styleSheet.insertRule(`body { ${radiiRules} }`, 'index-max') body.style.display = 'initial' - commit('setOption', { name: 'colors', value: colors }) - commit('setOption', { name: 'radii', value: radii }) + // commit('setOption', { name: 'colors', value: htmlColors }) + // commit('setOption', { name: 'radii', value: radii }) commit('setOption', { name: 'customTheme', value: col }) } +const generatePreset = (input) => { + const radii = input.radii || { + btnRadius: input.btnRadius, + inputRadius: input.inputRadius, + panelRadius: input.panelRadius, + avatarRadius: input.avatarRadius, + avatarAltRadius: input.avatarAltRadius, + tooltipRadius: input.tooltipRadius, + attachmentRadius: input.attachmentRadius + } + const colors = {} + + const col = Object.entries(input.colors || input).reduce((acc, [k, v]) => { + if (typeof v === 'object') { + acc[k] = v + } else { + acc[k] = hex2rgb(v) + } + return acc + }, {}) + + colors.fg = col.fg || col.text // text + colors.text = col.fg || col.text // text + colors.lightFg = col.fg || col.text // text + + colors.bg = col.bg // background + colors.lightBg = col.lightBg || brightness(5, colors.bg).rgb // hilighted bg + console.log(colors.bg) + console.log(colors.lightBg) + + colors.btn = col.btn || { r: 0, g: 0, b: 0 } + colors.btnText = getTextColor(colors.btn, colors.text) + + colors.panel = col.panel || col.btn + colors.panelText = getTextColor(colors.panel, colors.text) + + colors.topBar = col.topBar || col.btn + colors.topBarText = getTextColor(colors.topBar, colors.text) + + colors.input = col.input || Object.assign({ a: 0.5 }, col.btn) + colors.border = col.btn // borders + colors.faint = col.faint || Object.assign({ a: 0.5 }, col.text) + + colors.link = col.link // links + colors.icon = mixrgb(colors.bg, colors.text) // icons + + colors.cBlue = col.cBlue + colors.cRed = col.cRed + colors.cGreen = col.cGreen + colors.cOrange = col.cOrange + + colors.cAlertRed = col.cAlertRed || Object.assign({ a: 0.5 }, col.cRed) + + const htmlColors = Object.entries(colors) + .reduce((acc, [k, v]) => { + if (!v) return acc + acc[k] = typeof v.a === 'undefined' ? rgb2hex(v) : rgb2rgba(v) + return acc + }, {}) + + return { + colorRules: Object.entries(htmlColors).filter(([k, v]) => v).map(([k, v]) => `--${k}: ${v}`).join(';'), + radiiRules: Object.entries(radii).filter(([k, v]) => v).map(([k, v]) => `--${k}: ${v}px`).join(';'), + col + } +} + const setPreset = (val, commit) => { window.fetch('/static/styles.json') .then((data) => data.json()) @@ -148,7 +197,8 @@ const setPreset = (val, commit) => { const StyleSetter = { setStyle, setPreset, - setColors + setColors, + generatePreset } export default StyleSetter diff --git a/yarn.lock b/yarn.lock index fdad8b49..0139c714 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1205,6 +1205,10 @@ chokidar@^1.0.0, chokidar@^1.4.1: optionalDependencies: fsevents "^1.0.0" +chromatism@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chromatism/-/chromatism-3.0.0.tgz#a7249d353c1e4f3577e444ac41171c4e2e624b12" + chromedriver@^2.21.2: version "2.35.0" resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-2.35.0.tgz#c103ba2fb3d1671f666058159f5cbaa816902e4d"