commit: 9753db1c6798c0a581e9eebcdb3f46c4e075b780
parent 2a98ea6ddc5e3c602b4aad768e9452666438fb95
Author: Henry Jameson <me@hjkos.com>
Date: Sun, 29 Sep 2024 03:20:14 +0300
it works!
Diffstat:
4 files changed, 152 insertions(+), 14 deletions(-)
diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.js b/src/components/settings_modal/tabs/style_tab/style_tab.js
@@ -10,9 +10,15 @@ import ColorInput from 'src/components/color_input/color_input.vue'
import OpacityInput from 'src/components/opacity_input/opacity_input.vue'
import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import Popover from 'src/components/popover/popover.vue'
+import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue'
import { init } from 'src/services/theme_data/theme_data_3.service.js'
import { getCssRules } from 'src/services/theme_data/css_utils.js'
+import {
+ // rgb2hex,
+ hex2rgb,
+ getContrastRatio
+} from 'src/services/color_convert/color_convert.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faFloppyDisk, faFolderOpen, faFile } from '@fortawesome/free-solid-svg-icons'
@@ -40,7 +46,8 @@ export default {
TabSwitcher,
ShadowControl,
ColorInput,
- OpacityInput
+ OpacityInput,
+ ContrastRatio
},
setup () {
// Meta stuff
@@ -153,7 +160,10 @@ export default {
return scoped.join('\n')
})
- // Rules stuff aka meat and potatoes
+ // ### Rules stuff aka meat and potatoes
+ // The native structure of separate rules and the child -> parent
+ // relation isn't very convenient for editor, we replace the array
+ // and child -> parent structure with map and parent -> child structure
const editorFriendlyFallbackStructure = computed(() => {
const root = {}
@@ -251,27 +261,66 @@ export default {
const editedBackgroundColor = getEditedElement(null, 'background')
const editedOpacity = getEditedElement(null, 'opacity')
- const editedTextColor = getEditedElement('Text', 'color')
- const editedLinkColor = getEditedElement('Link', 'color')
- const editedIconColor = getEditedElement('Icon', 'color')
+ const editedTextColor = getEditedElement('Text', 'textColor')
+ const editedTextAuto = getEditedElement('Text', 'textAuto')
+ const editedLinkColor = getEditedElement('Link', 'textColor')
+ const editedIconColor = getEditedElement('Icon', 'textColor')
const editedShadow = getEditedElement(null, 'shadow')
const isBackgroundColorPresent = isElementPresent(null, 'background', '#FFFFFF')
const isOpacityPresent = isElementPresent(null, 'opacity', 1)
- const isTextColorPresent = isElementPresent('Text', 'color', '#000000')
- const isLinkColorPresent = isElementPresent('Link', 'color', '#000080')
- const isIconColorPresent = isElementPresent('Icon', 'color', '#909090')
+ const isTextColorPresent = isElementPresent('Text', 'textColor', '#000000')
+ const isTextAutoPresent = isElementPresent('Text', 'textAuto', '#000000')
+ const isLinkColorPresent = isElementPresent('Link', 'textColor', '#000080')
+ const isIconColorPresent = isElementPresent('Icon', 'textColor', '#909090')
const isShadowPresent = isElementPresent(null, 'shadow', [])
- const updateSelectedComponent = () => {
- selectedVariant.value = 'normal'
- selectedState.clear()
+ const editorFriendlyToOriginal = computed(() => {
+ const resultRules = []
+
+ const convert = (component, data = {}, parent) => {
+ const variants = Object.entries(data || {})
+
+ variants.forEach(([variant, variantData]) => {
+ const states = Object.entries(variantData)
+
+ states.forEach(([jointState, stateData]) => {
+ const state = jointState.split(/:/g)
+ const result = {
+ component,
+ variant,
+ state,
+ directives: stateData.directives || {}
+ }
+
+ console.log('PARENT', parent)
+ if (parent) {
+ result.parent = {
+ component: parent
+ }
+ }
+
+ resultRules.push(result)
+
+ // Currently we only support single depth for simplicity's sake
+ if (!parent) {
+ Object.entries(stateData._children || {}).forEach(([cName, child]) => convert(cName, child, component))
+ }
+ })
+ })
+ }
+ convert(selectedComponentName.value, allEditedRules[selectedComponentName.value])
+ console.log(toValue(allEditedRules))
+ console.log(toValue(resultRules))
+
+ return resultRules
+ })
+
+ const updatePreview = () => {
previewRules.splice(0, previewRules.length)
previewRules.push(...init({
- inputRuleset: [{
- component: selectedComponentName.value
- }],
+ inputRuleset: editorFriendlyToOriginal.value,
initialStaticVars: {
...palette
},
@@ -282,13 +331,48 @@ export default {
}).eager)
}
+ const updateSelectedComponent = () => {
+ selectedVariant.value = 'normal'
+ selectedState.clear()
+ updatePreview()
+ }
updateSelectedComponent()
watch(
+ allEditedRules,
+ updatePreview
+ )
+
+ watch(
selectedComponentName,
updateSelectedComponent
)
+ // TODO this is VERY primitive right now, need to make it
+ // support variables, fallbacks etc.
+ const getContrast = (bg, text) => {
+ console.log('CONTRAST', bg, text)
+ try {
+ const bgRgb = hex2rgb(bg)
+ const textRgb = hex2rgb(text)
+
+ const ratio = getContrastRatio(bgRgb, textRgb)
+ return {
+ 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
+ }
+ } catch (e) {
+ console.warn('Failure computing contrast', e)
+ return { error: e }
+ }
+ }
+
const isShadowTabOpen = ref(false)
const onTabSwitch = (tab) => {
isShadowTabOpen.value = tab === 'shadow'
@@ -318,12 +402,15 @@ export default {
editedBackgroundColor,
editedOpacity,
editedTextColor,
+ editedTextAuto,
editedLinkColor,
editedIconColor,
editedShadow,
+ getContrast,
isBackgroundColorPresent,
isOpacityPresent,
isTextColorPresent,
+ isTextAutoPresent,
isLinkColorPresent,
isIconColorPresent,
isShadowPresent,
diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.scss b/src/components/settings_modal/tabs/style_tab/style_tab.scss
@@ -11,6 +11,10 @@
line-height: 2;
}
+ &.suboption {
+ margin-left: 1em;
+ }
+
.opt {
margin: 0.5em;
}
diff --git a/src/components/settings_modal/tabs/style_tab/style_tab.vue b/src/components/settings_modal/tabs/style_tab/style_tab.vue
@@ -183,6 +183,47 @@
{{ $t('settings.style.themes3.editor.include_in_rule') }}
</template>
</Popover>
+ <div class="style-control suboption">
+ <label
+ for="textAuto"
+ class="label"
+ :class="{ faint: disabled || !present }"
+ >
+ {{ $t('settings.style.themes3.editor.text_auto.label') }}
+ </label>
+ <Select
+ id="textAuto"
+ v-model="editedTextAuto"
+ :disabled="!isTextAutoPresent"
+ >
+ <option value="no-preserve">
+ {{ $t('settings.style.themes3.editor.text_auto.no-preserve') }}
+ </option>
+ <option value="no-auto">
+ {{ $t('settings.style.themes3.editor.text_auto.no-auto') }}
+ </option>
+ <option value="preserve">
+ {{ $t('settings.style.themes3.editor.text_auto.preserve') }}
+ </option>
+ </Select>
+ </div>
+ <Popover
+ trigger="hover"
+ v-if="componentHas('Text')"
+ >
+ <template #trigger>
+ <Checkbox v-model="isTextAutoPresent" />
+ </template>
+ <template #content>
+ {{ $t('settings.style.themes3.editor.include_in_rule') }}
+ </template>
+ </Popover>
+ <div>
+ <ContrastRatio :contrast="getContrast(editedBackgroundColor, editedTextColor)" />
+ </div>
+ <div>
+ <!-- spacer for missing checkbox -->
+ </div>
<ColorInput
v-model="editedLinkColor"
:label="$t('settings.style.themes3.editor.link_color')"
diff --git a/src/i18n/en.json b/src/i18n/en.json
@@ -772,6 +772,12 @@
"icon_color": "Icon color",
"link_color": "Link color",
"include_in_rule": "Add to rule",
+ "text_auto": {
+ "label": "Auto-contrast",
+ "no-preserve": "Black or White",
+ "preserve": "Keep color",
+ "no-auto": "Disabled"
+ },
"components": {
"normal": {
"state": "Normal",