forked from AkkomaGang/akkoma-fe
collateral fixes, removed alpha control for alerts, added contrast text
generation for alerts, updated getTextColor to also have fallback to black/white if resulting contrast isn't passable (only when inverting lightness!), updated UI to use tabs.
This commit is contained in:
parent
1723f427f5
commit
e7fe2dc9f9
11 changed files with 200 additions and 187 deletions
|
@ -59,7 +59,12 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
logo () { return this.$store.state.instance.logo },
|
logo () { return this.$store.state.instance.logo },
|
||||||
style () { return { 'background-image': `url(${this.background})` } },
|
style () {
|
||||||
|
return {
|
||||||
|
'--body-background-image': `url(${this.background})`,
|
||||||
|
'background-image': `url(${this.background})`
|
||||||
|
}
|
||||||
|
},
|
||||||
sitename () { return this.$store.state.instance.name },
|
sitename () { return this.$store.state.instance.name },
|
||||||
chat () { return this.$store.state.chat.channel.state === 'joined' },
|
chat () { return this.$store.state.chat.channel.state === 'joined' },
|
||||||
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
|
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
|
||||||
|
|
|
@ -473,14 +473,19 @@ nav {
|
||||||
padding: 0.25em;
|
padding: 0.25em;
|
||||||
border-radius: $fallback--tooltipRadius;
|
border-radius: $fallback--tooltipRadius;
|
||||||
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
|
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
|
||||||
color: $fallback--faint;
|
|
||||||
color: var(--faint, $fallback--faint);
|
|
||||||
min-height: 28px;
|
min-height: 28px;
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
|
|
||||||
&.error {
|
&.error {
|
||||||
background-color: $fallback--alertError;
|
background-color: $fallback--alertError;
|
||||||
background-color: var(--alertError, $fallback--alertError);
|
background-color: var(--alertError, $fallback--alertError);
|
||||||
|
color: $fallback--text;
|
||||||
|
color: var(--alertErrorText, $fallback--text);
|
||||||
|
|
||||||
|
.panel-heading & {
|
||||||
|
color: $fallback--text;
|
||||||
|
color: var(--alertErrorPanelText, $fallback--text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<span class="contrast-ratio">
|
<span v-if="contrast" class="contrast-ratio">
|
||||||
<span :title="`Contrast is ${contrast.text}`" class="rating">
|
<span :title="`Contrast is ${contrast.text}`" class="rating">
|
||||||
<span v-if="contrast.aaa">
|
<span v-if="contrast.aaa">
|
||||||
<i class="icon-thumbs-up-alt"/>
|
<i class="icon-thumbs-up-alt"/>
|
||||||
|
@ -11,7 +11,7 @@
|
||||||
<i class="icon-attention"/>
|
<i class="icon-attention"/>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="rating" v-if="large" :title="`Contrast is ${contrast.text} (18pt+)`">
|
<span class="rating" v-if="contrast && large" :title="`Contrast is ${contrast.text} (18pt+)`">
|
||||||
<span v-if="contrast.aaa">
|
<span v-if="contrast.aaa">
|
||||||
<i class="icon-thumbs-up-alt"/>
|
<i class="icon-thumbs-up-alt"/>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -14,8 +14,8 @@
|
||||||
.icon-cancel,.delete-status {
|
.icon-cancel,.delete-status {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--cRed, $fallback--cRed);
|
|
||||||
color: $fallback--cRed;
|
color: $fallback--cRed;
|
||||||
|
color: var(--cRed, $fallback--cRed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -8,13 +8,13 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background-color: $fallback--cRed;
|
background-color: $fallback--cRed;
|
||||||
background-color: var(--badgeNotification, $fallback--cRed);
|
background-color: var(--badgeNotification, $fallback--cRed);
|
||||||
text-shadow: 0px 0px 3px rgba(0, 0, 0, 0.5);
|
|
||||||
border-radius: 99px;
|
border-radius: 99px;
|
||||||
min-width: 22px;
|
min-width: 22px;
|
||||||
max-width: 22px;
|
max-width: 22px;
|
||||||
min-height: 22px;
|
min-height: 22px;
|
||||||
max-height: 22px;
|
max-height: 22px;
|
||||||
color: white;
|
color: white;
|
||||||
|
color: var(--badgeNotificationText, white);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -27,8 +27,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.unseen {
|
.unseen {
|
||||||
box-shadow: inset 4px 0 0 var(--badgeNotification, $fallback--cRed);
|
|
||||||
padding-left: 0;
|
background-image: linear-gradient(135deg, var(--badgeNotification, $fallback--cRed) 4px, transparent 10px)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,8 +42,8 @@
|
||||||
.broken-favorite {
|
.broken-favorite {
|
||||||
border-radius: $fallback--tooltipRadius;
|
border-radius: $fallback--tooltipRadius;
|
||||||
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
|
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
|
||||||
color: $fallback--faint;
|
color: $fallback--text;
|
||||||
color: var(--faint, $fallback--faint);
|
color: var(--alertErrorText, $fallback--text);
|
||||||
background-color: $fallback--alertError;
|
background-color: $fallback--alertError;
|
||||||
background-color: var(--alertError, $fallback--alertError);
|
background-color: var(--alertError, $fallback--alertError);
|
||||||
padding: 2px .5em
|
padding: 2px .5em
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { rgb2hex, hex2rgb, getContrastRatio, worstCase } from '../../services/color_convert/color_convert.js'
|
import { rgb2hex, hex2rgb, getContrastRatio, alphaBlend } from '../../services/color_convert/color_convert.js'
|
||||||
import ColorInput from '../color_input/color_input.vue'
|
import ColorInput from '../color_input/color_input.vue'
|
||||||
import ContrastRatio from '../contrast_ratio/contrast_ratio.vue'
|
import ContrastRatio from '../contrast_ratio/contrast_ratio.vue'
|
||||||
import OpacityInput from '../opacity_input/opacity_input.vue'
|
import OpacityInput from '../opacity_input/opacity_input.vue'
|
||||||
import StyleSetter from '../../services/style_setter/style_setter.js'
|
import StyleSetter from '../../services/style_setter/style_setter.js'
|
||||||
|
import TabSwitcher from '../tab_switcher/tab_switcher.jsx'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data () {
|
data () {
|
||||||
|
@ -38,7 +39,6 @@ export default {
|
||||||
topBarTextColorLocal: undefined,
|
topBarTextColorLocal: undefined,
|
||||||
topBarLinkColorLocal: undefined,
|
topBarLinkColorLocal: undefined,
|
||||||
|
|
||||||
alertOpacityLocal: undefined,
|
|
||||||
alertErrorColorLocal: undefined,
|
alertErrorColorLocal: undefined,
|
||||||
|
|
||||||
badgeOpacityLocal: undefined,
|
badgeOpacityLocal: undefined,
|
||||||
|
@ -123,8 +123,6 @@ export default {
|
||||||
btn: this.btnOpacityLocal,
|
btn: this.btnOpacityLocal,
|
||||||
input: this.inputOpacityLocal,
|
input: this.inputOpacityLocal,
|
||||||
panel: this.panelOpacityLocal,
|
panel: this.panelOpacityLocal,
|
||||||
alert: this.alertOpacityLocal,
|
|
||||||
badge: this.badgeOpacityLocal,
|
|
||||||
topBar: this.topBarOpacityLocal,
|
topBar: this.topBarOpacityLocal,
|
||||||
border: this.borderOpacityLocal,
|
border: this.borderOpacityLocal,
|
||||||
faint: this.faintOpacityLocal
|
faint: this.faintOpacityLocal
|
||||||
|
@ -160,6 +158,7 @@ export default {
|
||||||
if (!this.previewTheme.colors) return {}
|
if (!this.previewTheme.colors) return {}
|
||||||
const colors = this.previewTheme.colors
|
const colors = this.previewTheme.colors
|
||||||
const opacity = this.previewTheme.opacity
|
const opacity = this.previewTheme.opacity
|
||||||
|
if (!colors.bg) return {}
|
||||||
const hints = (ratio) => ({
|
const hints = (ratio) => ({
|
||||||
text: ratio.toPrecision(3) + ':1',
|
text: ratio.toPrecision(3) + ':1',
|
||||||
// AA level, AAA level
|
// AA level, AAA level
|
||||||
|
@ -197,26 +196,28 @@ export default {
|
||||||
badgeNotification: hex2rgb(colors.badgeNotification)
|
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 = {
|
const ratios = {
|
||||||
bgText: getContrastRatio(worstCase(bgs.bg, opacity.bg, fgs.text), fgs.text),
|
bgText: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.text), fgs.text),
|
||||||
bgLink: getContrastRatio(worstCase(bgs.bg, opacity.bg, fgs.link), fgs.link),
|
bgLink: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.link), fgs.link),
|
||||||
bgRed: getContrastRatio(worstCase(bgs.bg, opacity.bg, fgs.red), fgs.red),
|
bgRed: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.red), fgs.red),
|
||||||
bgGreen: getContrastRatio(worstCase(bgs.bg, opacity.bg, fgs.green), fgs.green),
|
bgGreen: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.green), fgs.green),
|
||||||
bgBlue: getContrastRatio(worstCase(bgs.bg, opacity.bg, fgs.blue), fgs.blue),
|
bgBlue: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.blue), fgs.blue),
|
||||||
bgOrange: getContrastRatio(worstCase(bgs.bg, opacity.bg, fgs.orange), fgs.orange),
|
bgOrange: getContrastRatio(alphaBlend(bgs.bg, opacity.bg, fgs.orange), fgs.orange),
|
||||||
|
|
||||||
tintText: getContrastRatio(worstCase(bgs.bg, 0.5, fgs.panelText), fgs.text),
|
tintText: getContrastRatio(alphaBlend(bgs.bg, 0.5, fgs.panelText), fgs.text),
|
||||||
|
|
||||||
panelText: getContrastRatio(worstCase(bgs.panel, opacity.panel, fgs.panelText), fgs.panelText),
|
panelText: getContrastRatio(alphaBlend(bgs.panel, opacity.panel, fgs.panelText), fgs.panelText),
|
||||||
|
|
||||||
btnText: getContrastRatio(worstCase(bgs.btn, opacity.btn, fgs.btnText), fgs.btnText),
|
btnText: getContrastRatio(alphaBlend(bgs.btn, opacity.btn, fgs.btnText), fgs.btnText),
|
||||||
|
|
||||||
inputText: getContrastRatio(worstCase(bgs.input, opacity.input, fgs.inputText), fgs.inputText),
|
inputText: getContrastRatio(alphaBlend(bgs.input, opacity.input, fgs.inputText), fgs.inputText),
|
||||||
|
|
||||||
badgeNotification: getContrastRatio(worstCase(bgs.badgeNotification, opacity.badge, fgs.text), fgs.text),
|
topBarText: getContrastRatio(alphaBlend(bgs.topBar, opacity.topBar, fgs.topBarText), fgs.topBarText),
|
||||||
|
topBarLink: getContrastRatio(alphaBlend(bgs.topBar, opacity.topBar, fgs.topBarLink), fgs.topBarLink)
|
||||||
topBarText: getContrastRatio(worstCase(bgs.topBar, opacity.topBar, fgs.topBarText), fgs.topBarText),
|
|
||||||
topBarLink: getContrastRatio(worstCase(bgs.topBar, opacity.topBar, fgs.topBarLink), fgs.topBarLink)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {})
|
return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {})
|
||||||
|
@ -229,7 +230,8 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
ColorInput,
|
ColorInput,
|
||||||
OpacityInput,
|
OpacityInput,
|
||||||
ContrastRatio
|
ContrastRatio,
|
||||||
|
TabSwitcher
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
exportCurrentTheme () {
|
exportCurrentTheme () {
|
||||||
|
|
|
@ -24,8 +24,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="preview-container" :style="previewRules">
|
<div class="preview-container">
|
||||||
<div class="panel dummy">
|
<div class="panel dummy" :style="previewRules">
|
||||||
<div class="panel-heading">Preview</div>
|
<div class="panel-heading">Preview</div>
|
||||||
<div class="panel-body theme-preview-content">
|
<div class="panel-body theme-preview-content">
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
|
@ -45,132 +45,132 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="color-container">
|
|
||||||
<p>{{$t('settings.theme_help')}}</p>
|
<p>{{$t('settings.theme_help')}}</p>
|
||||||
<h3>Basic colors!!</h3>
|
<tab-switcher>
|
||||||
<div>
|
<div label="Basic" class="color-container">
|
||||||
<div class="color-item">
|
<div class="color-item">
|
||||||
<ColorInput name="bgColor" v-model="bgColorLocal" :label="$t('settings.background')"/>
|
<h4>Main colors</h4>
|
||||||
<OpacityInput name="bgOpacity" v-model="bgOpacityLocal" :fallback="previewTheme.opacity.bg || 1"/>
|
<ColorInput name="bgColor" v-model="bgColorLocal" :label="$t('settings.background')"/>
|
||||||
<ColorInput name="textColor" v-model="textColorLocal" :label="$t('settings.text')"/>
|
<OpacityInput name="bgOpacity" v-model="bgOpacityLocal" :fallback="previewTheme.opacity.bg || 1"/>
|
||||||
<ContrastRatio :contrast="previewContrast.bgText"/>
|
<ColorInput name="textColor" v-model="textColorLocal" :label="$t('settings.text')"/>
|
||||||
<ColorInput name="linkColor" v-model="linkColorLocal" :label="$t('settings.links')"/>
|
<ContrastRatio :contrast="previewContrast.bgText"/>
|
||||||
<ContrastRatio :contrast="previewContrast.bgLink"/>
|
<ColorInput name="linkColor" v-model="linkColorLocal" :label="$t('settings.links')"/>
|
||||||
|
<ContrastRatio :contrast="previewContrast.bgLink"/>
|
||||||
|
</div>
|
||||||
|
<div class="color-item">
|
||||||
|
<h4>Panel header, top bar, buttons, text fields</h4>
|
||||||
|
<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>See "Advanced" tab for more detailed control</p>
|
||||||
|
</div>
|
||||||
|
<div class="color-item">
|
||||||
|
<h4>Icons, alerts, etc.</h4>
|
||||||
|
<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">
|
||||||
|
<h4>.</h4>
|
||||||
|
<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>
|
||||||
</div>
|
</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"/>
|
|
||||||
</div>
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>More customs!</h3>
|
<div label="Advanced" class="color-container">
|
||||||
<div>
|
<div class="color-item">
|
||||||
<div class="color-item">
|
<h4>Alerts</h4>
|
||||||
<h4>Alerts</h4>
|
<ColorInput name="alertError" v-model="alertErrorColorLocal" :label="$t('settings.error')" :fallback="previewTheme.colors.alertError"/>
|
||||||
<ColorInput name="alertError" v-model="alertErrorColorLocal" :label="$t('settings.error')" :fallback="previewTheme.colors.alertError"/>
|
<ContrastRatio :contrast="previewContrast.alertError"/>
|
||||||
<OpacityInput name="alertOpacity" v-model="alertOpacityLocal" :fallback="previewTheme.opacity.alert || 1"/>
|
</div>
|
||||||
|
<div class="color-item">
|
||||||
|
<h4>Badges</h4>
|
||||||
|
<ColorInput name="badgeNotification" v-model="badgeNotificationColorLocal" :label="$t('settings.notification')" :fallback="previewTheme.colors.badgeNotification"/>
|
||||||
|
</div>
|
||||||
|
<div class="color-item">
|
||||||
|
<h4>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>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>Text fields</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>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>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>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.panel')"/>
|
||||||
|
<OpacityInput name="faintOpacity" v-model="faintOpacityLocal" :fallback="previewTheme.opacity.faint || 0.5"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="color-item">
|
<div label="Roundness" class="radius-container">
|
||||||
<h4>Alerts</h4>
|
<p>{{$t('settings.radii_help')}}</p>
|
||||||
<ColorInput name="badgeNotification" v-model="badgeNotificationColorLocal" :label="$t('settings.notification')" :fallback="previewTheme.colors.badgeNotification"/>
|
<div class="radius-item">
|
||||||
<ContrastRatio :contrast="previewContrast.badgeNotification"/>
|
<label for="btnradius" class="theme-radius-lb">{{$t('settings.btnRadius')}}</label>
|
||||||
<OpacityInput name="badgeOpacity" v-model="badgeOpacityLocal" :fallback="previewTheme.opacity.badge || 1"/>
|
<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>
|
||||||
<div class="color-item">
|
</tab-switcher>
|
||||||
<h4>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>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>Text fields</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>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>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>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.panel')"/>
|
|
||||||
<OpacityInput name="faintOpacity" v-model="faintOpacityLocal" :fallback="previewTheme.opacity.faint || 0.5"/>
|
|
||||||
</div>
|
|
||||||
</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">
|
<div class="apply-container">
|
||||||
<button class="btn submit" @click="setCustomTheme">{{$t('general.apply')}}</button>
|
<button class="btn submit" @click="setCustomTheme">{{$t('general.apply')}}</button>
|
||||||
|
@ -193,14 +193,12 @@
|
||||||
|
|
||||||
.apply-container,
|
.apply-container,
|
||||||
.radius-container,
|
.radius-container,
|
||||||
.color-container > div,
|
.color-container,
|
||||||
.presets-container {
|
.presets-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
flex: 2 0 100%;
|
margin-left: 1em
|
||||||
margin-top: 2em;
|
|
||||||
margin-bottom: .5em;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,7 +206,7 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-container > div{
|
.color-container{
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
@ -231,6 +229,9 @@
|
||||||
border-color: var(--border, $fallback--border);
|
border-color: var(--border, $fallback--border);
|
||||||
margin: 1em -1em 0;
|
margin: 1em -1em 0;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
|
background: var(--body-background-image);
|
||||||
|
background-size: cover;
|
||||||
|
background-position: 50% 50%;
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
|
|
|
@ -63,11 +63,6 @@
|
||||||
color: $fallback--faint;
|
color: $fallback--faint;
|
||||||
color: var(--panelFaint, $fallback--faint);
|
color: var(--panelFaint, $fallback--faint);
|
||||||
}
|
}
|
||||||
|
|
||||||
.loadmore-error {
|
|
||||||
color: $fallback--text;
|
|
||||||
color: var(--text, $fallback--text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.new-status-notification {
|
.new-status-notification {
|
||||||
|
|
|
@ -80,30 +80,23 @@ const getContrastRatio = (a, b) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This generates what "worst case" color would look like for transparent
|
* This performs alpha blending between solid background and semi-transparent foreground
|
||||||
* segments. I.e. transparent black with yellow text over yellow background.
|
|
||||||
*
|
*
|
||||||
* @param {Object} srgb - transparent color
|
* @param {Object} fg - top layer color
|
||||||
* @param {Number} alpha - color's opacity/alpha channel
|
* @param {Number} fga - top layer's alpha
|
||||||
* @param {Object} textSrgb - text color (considered as worst case scenario for transparent background)
|
* @param {Object} bg - bottom layer color
|
||||||
* @returns {Object} sRGB of resulting color
|
* @returns {Object} sRGB of resulting color
|
||||||
*/
|
*/
|
||||||
const transparentWorstCase = (srgb, alpha, textSrgb) => {
|
const alphaBlend = (fg, fga, bg) => {
|
||||||
|
if (fga === 1 || typeof fga === 'undefined') return fg
|
||||||
return 'rgb'.split('').reduce((acc, c) => {
|
return 'rgb'.split('').reduce((acc, c) => {
|
||||||
// Simplified https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
|
// Simplified https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
|
||||||
// for opaque bg and transparent fg
|
// for opaque bg and transparent fg
|
||||||
acc[c] = (srgb[c] * alpha + textSrgb[c] * (1 - alpha))
|
acc[c] = (fg[c] * fga + bg[c] * (1 - fga))
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
const worstCase = (bg, bga, text) => {
|
|
||||||
console.log(bg)
|
|
||||||
console.log(text)
|
|
||||||
if (bga === 1 || typeof bga === 'undefined') return bg
|
|
||||||
return transparentWorstCase(bg, bga, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
const hex2rgb = (hex) => {
|
const hex2rgb = (hex) => {
|
||||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||||
return result ? {
|
return result ? {
|
||||||
|
@ -134,5 +127,5 @@ export {
|
||||||
mixrgb,
|
mixrgb,
|
||||||
rgbstr2hex,
|
rgbstr2hex,
|
||||||
getContrastRatio,
|
getContrastRatio,
|
||||||
worstCase
|
alphaBlend
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { times } from 'lodash'
|
import { times } from 'lodash'
|
||||||
import { brightness, invertLightness, convert } from 'chromatism'
|
import { brightness, invertLightness, convert, contrastRatio } from 'chromatism'
|
||||||
import { rgb2hex, hex2rgb, mixrgb } from '../color_convert/color_convert.js'
|
import { rgb2hex, hex2rgb, mixrgb, getContrastRatio, alphaBlend } from '../color_convert/color_convert.js'
|
||||||
|
|
||||||
// While this is not used anymore right now, I left it in if we want to do custom
|
// 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
|
// styles that aren't just colors, so user can pick from a few different distinct
|
||||||
|
@ -58,13 +58,17 @@ const rgb2rgba = function (rgba) {
|
||||||
return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`
|
return `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTextColor = function (bg, text) {
|
const getTextColor = function (bg, text, preserve) {
|
||||||
const bgIsLight = convert(bg).hsl.l > 50
|
const bgIsLight = convert(bg).hsl.l > 50
|
||||||
const textIsLight = convert(text).hsl.l > 50
|
const textIsLight = convert(text).hsl.l > 50
|
||||||
|
|
||||||
if ((bgIsLight && textIsLight) || (!bgIsLight && !textIsLight)) {
|
if ((bgIsLight && textIsLight) || (!bgIsLight && !textIsLight)) {
|
||||||
const base = typeof text.a !== 'undefined' ? { a: text.a } : {}
|
const base = typeof text.a !== 'undefined' ? { a: text.a } : {}
|
||||||
return Object.assign(base, invertLightness(text).rgb)
|
const result = Object.assign(base, invertLightness(text).rgb)
|
||||||
|
if (!preserve && getContrastRatio(bg, result) < 4.5) {
|
||||||
|
return contrastRatio(bg, text).rgb
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
@ -104,7 +108,12 @@ const generatePreset = (input) => {
|
||||||
alert: 0.5,
|
alert: 0.5,
|
||||||
input: 0.5,
|
input: 0.5,
|
||||||
faint: 0.5
|
faint: 0.5
|
||||||
}, input.opacity)
|
}, Object.entries(input.opacity || {}).reduce((acc, [k, v]) => {
|
||||||
|
if (typeof v !== 'undefined') {
|
||||||
|
acc[k] = v
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {}))
|
||||||
|
|
||||||
const col = Object.entries(input.colors || input).reduce((acc, [k, v]) => {
|
const col = Object.entries(input.colors || input).reduce((acc, [k, v]) => {
|
||||||
if (typeof v === 'object') {
|
if (typeof v === 'object') {
|
||||||
|
@ -128,9 +137,9 @@ const generatePreset = (input) => {
|
||||||
|
|
||||||
colors.fg = col.fg
|
colors.fg = col.fg
|
||||||
colors.fgText = col.fgText || getTextColor(colors.fg, colors.text)
|
colors.fgText = col.fgText || getTextColor(colors.fg, colors.text)
|
||||||
colors.fgLink = col.fgLink || getTextColor(colors.fg, colors.link)
|
colors.fgLink = col.fgLink || getTextColor(colors.fg, colors.link, true)
|
||||||
|
|
||||||
colors.border = col.border || brightness(20 * mod, colors.fg).rgb
|
colors.border = col.border || brightness(2 * mod, colors.fg).rgb
|
||||||
|
|
||||||
colors.btn = col.btn || Object.assign({}, col.fg)
|
colors.btn = col.btn || Object.assign({}, col.fg)
|
||||||
colors.btnText = col.btnText || getTextColor(colors.btn, colors.fgText)
|
colors.btnText = col.btnText || getTextColor(colors.btn, colors.fgText)
|
||||||
|
@ -156,8 +165,11 @@ const generatePreset = (input) => {
|
||||||
colors.cOrange = col.cOrange
|
colors.cOrange = col.cOrange
|
||||||
|
|
||||||
colors.alertError = col.alertError || Object.assign({}, col.cRed)
|
colors.alertError = col.alertError || Object.assign({}, col.cRed)
|
||||||
|
colors.alertErrorText = getTextColor(alphaBlend(colors.alertError, opacity.alert, colors.bg), colors.text)
|
||||||
|
colors.alertErrorPanelText = getTextColor(alphaBlend(colors.alertError, opacity.alert, colors.panel), colors.panelText)
|
||||||
|
|
||||||
colors.badgeNotification = col.badgeNotification || Object.assign({}, col.cRed)
|
colors.badgeNotification = col.badgeNotification || Object.assign({}, col.cRed)
|
||||||
colors.badgeNotificationText = col.badgeNotification || Object.assign({}, col.cRed)
|
colors.badgeNotificationText = contrastRatio(colors.badgeNotification).rgb
|
||||||
|
|
||||||
Object.entries(opacity).forEach(([ k, v ]) => {
|
Object.entries(opacity).forEach(([ k, v ]) => {
|
||||||
if (typeof v === 'undefined') return
|
if (typeof v === 'undefined') return
|
||||||
|
|
|
@ -11,7 +11,7 @@ const highlightStyle = (prefs) => {
|
||||||
if (type === 'striped') {
|
if (type === 'striped') {
|
||||||
return {
|
return {
|
||||||
backgroundImage: [
|
backgroundImage: [
|
||||||
'repeating-linear-gradient(-45deg,',
|
'repeating-linear-gradient(135deg,',
|
||||||
`${tintColor} ,`,
|
`${tintColor} ,`,
|
||||||
`${tintColor} 20px,`,
|
`${tintColor} 20px,`,
|
||||||
`${tintColor2} 20px,`,
|
`${tintColor2} 20px,`,
|
||||||
|
|
Loading…
Reference in a new issue