Merge branch 'themes3-grand-finale-maybe' into 'develop'
Themes 3 See merge request pleroma/pleroma-fe!1951
This commit is contained in:
commit
cbe9427123
76 changed files with 4827 additions and 1236 deletions
|
|
@ -2,15 +2,23 @@ import utf8 from 'utf8'
|
|||
|
||||
export const newExporter = ({
|
||||
filename = 'data',
|
||||
mime = 'application/json',
|
||||
extension = '.json',
|
||||
getExportedObject
|
||||
}) => ({
|
||||
exportData () {
|
||||
const stringified = utf8.encode(JSON.stringify(getExportedObject(), null, 2)) // Pretty-print and indent with 2 spaces
|
||||
let stringified
|
||||
if (mime === 'application/json') {
|
||||
stringified = utf8.encode(JSON.stringify(getExportedObject(), null, 2)) // Pretty-print and indent with 2 spaces
|
||||
} else {
|
||||
stringified = utf8.encode(getExportedObject()) // 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', `${filename}.json`)
|
||||
e.setAttribute('href', 'data:application/json;base64,' + window.btoa(stringified))
|
||||
const realFilename = typeof filename === 'function' ? filename() : filename
|
||||
e.setAttribute('download', `${realFilename}.${extension}`)
|
||||
e.setAttribute('href', `data:${mime};base64, ${window.btoa(stringified)}`)
|
||||
e.style.display = 'none'
|
||||
|
||||
document.body.appendChild(e)
|
||||
|
|
@ -20,6 +28,8 @@ export const newExporter = ({
|
|||
})
|
||||
|
||||
export const newImporter = ({
|
||||
accept = '.json',
|
||||
parser = (string) => JSON.parse(string),
|
||||
onImport,
|
||||
onImportFailure,
|
||||
validator = () => true
|
||||
|
|
@ -27,18 +37,19 @@ export const newImporter = ({
|
|||
importData () {
|
||||
const filePicker = document.createElement('input')
|
||||
filePicker.setAttribute('type', 'file')
|
||||
filePicker.setAttribute('accept', '.json')
|
||||
filePicker.setAttribute('accept', accept)
|
||||
|
||||
filePicker.addEventListener('change', event => {
|
||||
if (event.target.files[0]) {
|
||||
const filename = event.target.files[0].name
|
||||
// eslint-disable-next-line no-undef
|
||||
const reader = new FileReader()
|
||||
reader.onload = ({ target }) => {
|
||||
try {
|
||||
const parsed = JSON.parse(target.result)
|
||||
const validationResult = validator(parsed)
|
||||
const parsed = parser(target.result, filename)
|
||||
const validationResult = validator(parsed, filename)
|
||||
if (validationResult === true) {
|
||||
onImport(parsed)
|
||||
onImport(parsed, filename)
|
||||
} else {
|
||||
onImportFailure({ validationResult })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { hex2rgb } from '../color_convert/color_convert.js'
|
||||
import { init, getEngineChecksum } from '../theme_data/theme_data_3.service.js'
|
||||
import { getCssRules } from '../theme_data/css_utils.js'
|
||||
import { defaultState } from '../../modules/config.js'
|
||||
import { chunk } from 'lodash'
|
||||
import pako from 'pako'
|
||||
import localforage from 'localforage'
|
||||
|
||||
// On platforms where this is not supported, it will return undefined
|
||||
// Otherwise it will return an array
|
||||
|
|
@ -52,29 +53,12 @@ export const generateTheme = (inputRuleset, callbacks, debug) => {
|
|||
|
||||
const themes3 = init({
|
||||
inputRuleset,
|
||||
// Assuming that "worst case scenario background" is panel background since it's the most likely one
|
||||
ultimateBackgroundColor: inputRuleset[0].directives['--bg'].split('|')[1].trim(),
|
||||
debug
|
||||
})
|
||||
|
||||
getCssRules(themes3.eager, debug).forEach(rule => {
|
||||
// Hacks to support multiple selectors on same component
|
||||
if (rule.match(/::-webkit-scrollbar-button/)) {
|
||||
const parts = rule.split(/[{}]/g)
|
||||
const newRule = [
|
||||
parts[0],
|
||||
', ',
|
||||
parts[0].replace(/button/, 'thumb'),
|
||||
', ',
|
||||
parts[0].replace(/scrollbar-button/, 'resizer'),
|
||||
' {',
|
||||
parts[1],
|
||||
'}'
|
||||
].join('')
|
||||
onNewRule(newRule, false)
|
||||
} else {
|
||||
onNewRule(rule, false)
|
||||
}
|
||||
onNewRule(rule, false)
|
||||
})
|
||||
onEagerFinished()
|
||||
|
||||
|
|
@ -88,22 +72,7 @@ export const generateTheme = (inputRuleset, callbacks, debug) => {
|
|||
const chunk = chunks[counter]
|
||||
Promise.all(chunk.map(x => x())).then(result => {
|
||||
getCssRules(result.filter(x => x), debug).forEach(rule => {
|
||||
if (rule.match(/\.modal-view/)) {
|
||||
const parts = rule.split(/[{}]/g)
|
||||
const newRule = [
|
||||
parts[0],
|
||||
', ',
|
||||
parts[0].replace(/\.modal-view/, '#modal'),
|
||||
', ',
|
||||
parts[0].replace(/\.modal-view/, '.shout-panel'),
|
||||
' {',
|
||||
parts[1],
|
||||
'}'
|
||||
].join('')
|
||||
onNewRule(newRule, true)
|
||||
} else {
|
||||
onNewRule(rule, true)
|
||||
}
|
||||
onNewRule(rule, true)
|
||||
})
|
||||
// const t1 = performance.now()
|
||||
// console.debug('Chunk ' + counter + ' took ' + (t1 - t0) + 'ms')
|
||||
|
|
@ -120,12 +89,15 @@ export const generateTheme = (inputRuleset, callbacks, debug) => {
|
|||
return { lazyProcessFunc: processChunk }
|
||||
}
|
||||
|
||||
export const tryLoadCache = () => {
|
||||
const json = localStorage.getItem('pleroma-fe-theme-cache')
|
||||
if (!json) return null
|
||||
export const tryLoadCache = async () => {
|
||||
console.info('Trying to load compiled theme data from cache')
|
||||
const data = await localforage.getItem('pleromafe-theme-cache')
|
||||
if (!data) return null
|
||||
let cache
|
||||
try {
|
||||
cache = JSON.parse(json)
|
||||
const decoded = new TextDecoder().decode(pako.inflate(data))
|
||||
cache = JSON.parse(decoded)
|
||||
console.info(`Loaded theme from cache, size=${cache}`)
|
||||
} catch (e) {
|
||||
console.error('Failed to decode theme cache:', e)
|
||||
return false
|
||||
|
|
@ -150,16 +122,28 @@ export const applyTheme = (input, onFinish = (data) => {}, debug) => {
|
|||
const eagerStyles = createStyleSheet(EAGER_STYLE_ID)
|
||||
const lazyStyles = createStyleSheet(LAZY_STYLE_ID)
|
||||
|
||||
const insertRule = (styles, rule) => {
|
||||
if (rule.indexOf('webkit') >= 0) {
|
||||
try {
|
||||
styles.sheet.insertRule(rule, 'index-max')
|
||||
styles.rules.push(rule)
|
||||
} catch (e) {
|
||||
console.warn('Can\'t insert rule due to lack of support', e)
|
||||
}
|
||||
} else {
|
||||
styles.sheet.insertRule(rule, 'index-max')
|
||||
styles.rules.push(rule)
|
||||
}
|
||||
}
|
||||
|
||||
const { lazyProcessFunc } = generateTheme(
|
||||
input,
|
||||
{
|
||||
onNewRule (rule, isLazy) {
|
||||
if (isLazy) {
|
||||
lazyStyles.sheet.insertRule(rule, 'index-max')
|
||||
lazyStyles.rules.push(rule)
|
||||
insertRule(lazyStyles, rule)
|
||||
} else {
|
||||
eagerStyles.sheet.insertRule(rule, 'index-max')
|
||||
eagerStyles.rules.push(rule)
|
||||
insertRule(eagerStyles, rule)
|
||||
}
|
||||
},
|
||||
onEagerFinished () {
|
||||
|
|
@ -169,16 +153,10 @@ export const applyTheme = (input, onFinish = (data) => {}, debug) => {
|
|||
adoptStyleSheets([eagerStyles, lazyStyles])
|
||||
const cache = { engineChecksum: getEngineChecksum(), data: [eagerStyles.rules, lazyStyles.rules] }
|
||||
onFinish(cache)
|
||||
try {
|
||||
localStorage.setItem('pleroma-fe-theme-cache', JSON.stringify(cache))
|
||||
} catch (e) {
|
||||
localStorage.removeItem('pleroma-fe-theme-cache')
|
||||
try {
|
||||
localStorage.setItem('pleroma-fe-theme-cache', JSON.stringify(cache))
|
||||
} catch (e) {
|
||||
console.warn('cannot save cache!', e)
|
||||
}
|
||||
const compress = (js) => {
|
||||
return pako.deflate(JSON.stringify(js))
|
||||
}
|
||||
localforage.setItem('pleromafe-theme-cache', compress(cache))
|
||||
}
|
||||
},
|
||||
debug
|
||||
|
|
@ -252,64 +230,66 @@ export const applyConfig = (input, i18n) => {
|
|||
styleSheet.toString()
|
||||
styleSheet.insertRule(`:root { ${rules} }`, 'index-max')
|
||||
|
||||
// TODO find a way to make this not apply to theme previews
|
||||
if (Object.prototype.hasOwnProperty.call(config, 'forcedRoundness')) {
|
||||
styleSheet.insertRule(` * {
|
||||
styleSheet.insertRule(` *:not(.preview-block) {
|
||||
--roundness: var(--forcedRoundness) !important;
|
||||
}`, 'index-max')
|
||||
}
|
||||
}
|
||||
|
||||
export const getThemes = () => {
|
||||
export const getResourcesIndex = async (url, parser = JSON.parse) => {
|
||||
const cache = 'no-store'
|
||||
const customUrl = url.replace(/\.(\w+)$/, '.custom.$1')
|
||||
let builtin
|
||||
let custom
|
||||
|
||||
return window.fetch('/static/styles.json', { cache })
|
||||
.then((data) => data.json())
|
||||
.then((themes) => {
|
||||
return Object.entries(themes).map(([k, v]) => {
|
||||
let promise = null
|
||||
const resourceTransform = (resources) => {
|
||||
return Object
|
||||
.entries(resources)
|
||||
.map(([k, v]) => {
|
||||
if (typeof v === 'object') {
|
||||
promise = Promise.resolve(v)
|
||||
return [k, () => Promise.resolve(v)]
|
||||
} else if (typeof v === 'string') {
|
||||
promise = window.fetch(v, { cache })
|
||||
.then((data) => data.json())
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
return null
|
||||
})
|
||||
return [
|
||||
k,
|
||||
() => window
|
||||
.fetch(v, { cache })
|
||||
.then(data => data.text())
|
||||
.then(text => parser(text))
|
||||
.catch(e => {
|
||||
console.error(e)
|
||||
return null
|
||||
})
|
||||
]
|
||||
} else {
|
||||
console.error(`Unknown resource format - ${k} is a ${typeof v}`)
|
||||
return [k, null]
|
||||
}
|
||||
return [k, promise]
|
||||
})
|
||||
})
|
||||
.then((promises) => {
|
||||
return promises
|
||||
.reduce((acc, [k, v]) => {
|
||||
acc[k] = v
|
||||
return acc
|
||||
}, {})
|
||||
})
|
||||
}
|
||||
|
||||
export const getPreset = (val) => {
|
||||
return getThemes()
|
||||
.then((themes) => themes[val] ? themes[val] : themes['pleroma-dark'])
|
||||
.then((theme) => {
|
||||
const isV1 = Array.isArray(theme)
|
||||
const data = isV1 ? {} : theme.theme
|
||||
|
||||
if (isV1) {
|
||||
const bg = hex2rgb(theme[1])
|
||||
const fg = hex2rgb(theme[2])
|
||||
const text = hex2rgb(theme[3])
|
||||
const link = hex2rgb(theme[4])
|
||||
|
||||
const cRed = hex2rgb(theme[5] || '#FF0000')
|
||||
const cGreen = hex2rgb(theme[6] || '#00FF00')
|
||||
const cBlue = hex2rgb(theme[7] || '#0000FF')
|
||||
const cOrange = hex2rgb(theme[8] || '#E3FF00')
|
||||
|
||||
data.colors = { bg, fg, text, link, cRed, cBlue, cGreen, cOrange }
|
||||
}
|
||||
|
||||
return { theme: data, source: theme.source }
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const builtinData = await window.fetch(url, { cache })
|
||||
const builtinResources = await builtinData.json()
|
||||
builtin = resourceTransform(builtinResources)
|
||||
} catch (e) {
|
||||
builtin = []
|
||||
console.warn(`Builtin resources at ${url} unavailable`)
|
||||
}
|
||||
|
||||
try {
|
||||
const customData = await window.fetch(customUrl, { cache })
|
||||
const customResources = await customData.json()
|
||||
custom = resourceTransform(customResources)
|
||||
} catch (e) {
|
||||
custom = []
|
||||
console.warn(`Custom resources at ${customUrl} unavailable`)
|
||||
}
|
||||
|
||||
const total = [...custom, ...builtin]
|
||||
if (total.length === 0) {
|
||||
return Promise.reject(new Error(`Resource at ${url} and ${customUrl} completely unavailable. Panicking`))
|
||||
}
|
||||
return Promise.resolve(Object.fromEntries(total))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,25 +2,6 @@ import { convert } from 'chromatism'
|
|||
|
||||
import { hex2rgb, rgba2css } from '../color_convert/color_convert.js'
|
||||
|
||||
export const parseCssShadow = (text) => {
|
||||
const dimensions = /(\d[a-z]*\s?){2,4}/.exec(text)?.[0]
|
||||
const inset = /inset/.exec(text)?.[0]
|
||||
const color = text.replace(dimensions, '').replace(inset, '')
|
||||
|
||||
const [x, y, blur = 0, spread = 0] = dimensions.split(/ /).filter(x => x).map(x => x.trim())
|
||||
const isInset = inset?.trim() === 'inset'
|
||||
const colorString = color.split(/ /).filter(x => x).map(x => x.trim())[0]
|
||||
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
blur,
|
||||
spread,
|
||||
inset: isInset,
|
||||
color: colorString
|
||||
}
|
||||
}
|
||||
|
||||
export const getCssColorString = (color, alpha = 1) => rgba2css({ ...convert(color).rgb, a: alpha })
|
||||
|
||||
export const getCssShadow = (input, usesDropShadow) => {
|
||||
|
|
@ -84,6 +65,9 @@ export const getCssRules = (rules, debug) => rules.map(rule => {
|
|||
].join(';\n ')
|
||||
}
|
||||
case 'shadow': {
|
||||
if (!rule.dynamicVars.shadow) {
|
||||
return ''
|
||||
}
|
||||
return ' ' + [
|
||||
'--shadow: ' + getCssShadow(rule.dynamicVars.shadow),
|
||||
'--shadowFilter: ' + getCssShadowFilter(rule.dynamicVars.shadow),
|
||||
|
|
@ -98,7 +82,7 @@ export const getCssRules = (rules, debug) => rules.map(rule => {
|
|||
`
|
||||
}
|
||||
if (v === 'transparent') {
|
||||
if (rule.component === 'Root') return []
|
||||
if (rule.component === 'Root') return null
|
||||
return [
|
||||
rule.directives.backgroundNoCssColor !== 'yes' ? ('background-color: ' + v) : '',
|
||||
' --background: ' + v
|
||||
|
|
@ -130,7 +114,7 @@ export const getCssRules = (rules, debug) => rules.map(rule => {
|
|||
}
|
||||
default:
|
||||
if (k.startsWith('--')) {
|
||||
const [type, value] = v.split('|').map(x => x.trim()) // woah, Extreme!
|
||||
const [type, value] = v.split('|').map(x => x.trim())
|
||||
switch (type) {
|
||||
case 'color': {
|
||||
const color = rule.dynamicVars[k]
|
||||
|
|
@ -143,21 +127,20 @@ export const getCssRules = (rules, debug) => rules.map(rule => {
|
|||
case 'generic':
|
||||
return k + ': ' + value
|
||||
default:
|
||||
return ''
|
||||
return null
|
||||
}
|
||||
}
|
||||
return ''
|
||||
return null
|
||||
}
|
||||
}).filter(x => x).map(x => ' ' + x).join(';\n')
|
||||
}).filter(x => x).map(x => ' ' + x + ';').join('\n')
|
||||
|
||||
return [
|
||||
header,
|
||||
directives + ';',
|
||||
directives,
|
||||
(rule.component === 'Text' && rule.state.indexOf('faint') < 0 && rule.directives.textNoCssColor !== 'yes') ? ' color: var(--text);' : '',
|
||||
'',
|
||||
virtualDirectives,
|
||||
footer
|
||||
].join('\n')
|
||||
].filter(x => x).join('\n')
|
||||
}).filter(x => x)
|
||||
|
||||
export const getScopedVersion = (rules, newScope) => {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { flattenDeep } from 'lodash'
|
||||
|
||||
const parseShadow = string => {
|
||||
const modes = ['_full', 'inset', 'x', 'y', 'blur', 'spread', 'color', 'alpha']
|
||||
export const deserializeShadow = string => {
|
||||
const modes = ['_full', 'inset', 'x', 'y', 'blur', 'spread', 'color', 'alpha', 'name']
|
||||
const regexPrep = [
|
||||
// inset keyword (optional)
|
||||
'^(?:(inset)\\s+)?',
|
||||
'^',
|
||||
'(?:(inset)\\s+)?',
|
||||
// x
|
||||
'(?:(-?[0-9]+(?:\\.[0-9]+)?)\\s+)',
|
||||
// y
|
||||
|
|
@ -14,19 +15,31 @@ const parseShadow = string => {
|
|||
// spread (optional)
|
||||
'(?:(-?[0-9]+(?:\\.[0-9]+)?)\\s+)?',
|
||||
// either hex, variable or function
|
||||
'(#[0-9a-f]{6}|--[a-z\\-_]+|\\$[a-z\\-()_]+)',
|
||||
'(#[0-9a-f]{6}|--[a-z0-9\\-_]+|\\$[a-z0-9\\-()_ ]+)',
|
||||
// opacity (optional)
|
||||
'(?:\\s+\\/\\s+([0-9]+(?:\\.[0-9]+)?)\\s*)?$'
|
||||
'(?:\\s+\\/\\s+([0-9]+(?:\\.[0-9]+)?)\\s*)?',
|
||||
// name
|
||||
'(?:\\s+#(\\w+)\\s*)?',
|
||||
'$'
|
||||
].join('')
|
||||
const regex = new RegExp(regexPrep, 'gis') // global, (stable) indices, single-string
|
||||
const result = regex.exec(string)
|
||||
if (result == null) {
|
||||
return string
|
||||
if (string.startsWith('$') || string.startsWith('--')) {
|
||||
return string
|
||||
} else {
|
||||
throw new Error(`Invalid shadow definition: '${string}'`)
|
||||
}
|
||||
} else {
|
||||
const numeric = new Set(['x', 'y', 'blur', 'spread', 'alpha'])
|
||||
const { x, y, blur, spread, alpha, inset, color } = Object.fromEntries(modes.map((mode, i) => {
|
||||
const { x, y, blur, spread, alpha, inset, color, name } = Object.fromEntries(modes.map((mode, i) => {
|
||||
if (numeric.has(mode)) {
|
||||
return [mode, Number(result[i])]
|
||||
const number = Number(result[i])
|
||||
if (Number.isNaN(number)) {
|
||||
if (mode === 'alpha') return [mode, 1]
|
||||
return [mode, 0]
|
||||
}
|
||||
return [mode, number]
|
||||
} else if (mode === 'inset') {
|
||||
return [mode, !!result[i]]
|
||||
} else {
|
||||
|
|
@ -34,7 +47,7 @@ const parseShadow = string => {
|
|||
}
|
||||
}).filter(([k, v]) => v !== false).slice(1))
|
||||
|
||||
return { x, y, blur, spread, color, alpha, inset }
|
||||
return { x, y, blur, spread, color, alpha, inset, name }
|
||||
}
|
||||
}
|
||||
// this works nearly the same as HTML tree converter
|
||||
|
|
@ -136,12 +149,12 @@ export const deserialize = (input) => {
|
|||
|
||||
output.directives = Object.fromEntries(content.map(d => {
|
||||
const [property, value] = d.split(':')
|
||||
let realValue = value.trim()
|
||||
let realValue = (value || '').trim()
|
||||
if (property === 'shadow') {
|
||||
if (realValue === 'none') {
|
||||
realValue = []
|
||||
} else {
|
||||
realValue = value.split(',').map(v => parseShadow(v.trim()))
|
||||
realValue = value.split(',').map(v => deserializeShadow(v.trim()))
|
||||
}
|
||||
} if (!Number.isNaN(Number(value))) {
|
||||
realValue = Number(value)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
import { unroll } from './iss_utils.js'
|
||||
import { deserializeShadow } from './iss_deserializer.js'
|
||||
|
||||
const serializeShadow = s => {
|
||||
export const serializeShadow = (s, throwOnInvalid) => {
|
||||
if (typeof s === 'object') {
|
||||
return `${s.inset ? 'inset ' : ''}${s.x} ${s.y} ${s.blur} ${s.spread} ${s.color} / ${s.alpha}`
|
||||
const inset = s.inset ? 'inset ' : ''
|
||||
const name = s.name ? ` #${s.name} ` : ''
|
||||
const result = `${inset}${s.x} ${s.y} ${s.blur} ${s.spread} ${s.color} / ${s.alpha}${name}`
|
||||
deserializeShadow(result) // Verify that output is valid and parseable
|
||||
return result
|
||||
} else {
|
||||
return s
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,43 +56,74 @@ export const getAllPossibleCombinations = (array) => {
|
|||
*
|
||||
* @returns {String} CSS selector (or path)
|
||||
*/
|
||||
export const genericRuleToSelector = components => (rule, ignoreOutOfTreeSelector, isParent) => {
|
||||
export const genericRuleToSelector = components => (rule, ignoreOutOfTreeSelector, liteMode, children) => {
|
||||
const isParent = !!children
|
||||
if (!rule && !isParent) return null
|
||||
const component = components[rule.component]
|
||||
const { states = {}, variants = {}, selector, outOfTreeSelector } = component
|
||||
const { states = {}, variants = {}, outOfTreeSelector } = component
|
||||
|
||||
const applicableStates = ((rule.state || []).filter(x => x !== 'normal')).map(state => states[state])
|
||||
const expand = (array = [], subArray = []) => {
|
||||
if (array.length === 0) return subArray.map(x => [x])
|
||||
if (subArray.length === 0) return array.map(x => [x])
|
||||
return array.map(a => {
|
||||
return subArray.map(b => [a, b])
|
||||
}).flat()
|
||||
}
|
||||
|
||||
let componentSelectors = Array.isArray(component.selector) ? component.selector : [component.selector]
|
||||
if (ignoreOutOfTreeSelector || liteMode) componentSelectors = [componentSelectors[0]]
|
||||
componentSelectors = componentSelectors.map(selector => {
|
||||
if (selector === ':root') {
|
||||
return ''
|
||||
} else if (isParent) {
|
||||
return selector
|
||||
} else {
|
||||
if (outOfTreeSelector && !ignoreOutOfTreeSelector) return outOfTreeSelector
|
||||
return selector
|
||||
}
|
||||
})
|
||||
|
||||
const applicableVariantName = (rule.variant || 'normal')
|
||||
let applicableVariant = ''
|
||||
let variantSelectors = null
|
||||
if (applicableVariantName !== 'normal') {
|
||||
applicableVariant = variants[applicableVariantName]
|
||||
variantSelectors = variants[applicableVariantName]
|
||||
} else {
|
||||
applicableVariant = variants?.normal ?? ''
|
||||
variantSelectors = variants?.normal ?? ''
|
||||
}
|
||||
variantSelectors = Array.isArray(variantSelectors) ? variantSelectors : [variantSelectors]
|
||||
if (ignoreOutOfTreeSelector || liteMode) variantSelectors = [variantSelectors[0]]
|
||||
|
||||
let realSelector
|
||||
if (selector === ':root') {
|
||||
realSelector = ''
|
||||
} else if (isParent) {
|
||||
realSelector = selector
|
||||
} else {
|
||||
if (outOfTreeSelector && !ignoreOutOfTreeSelector) realSelector = outOfTreeSelector
|
||||
else realSelector = selector
|
||||
}
|
||||
const applicableStates = (rule.state || []).filter(x => x !== 'normal')
|
||||
// const applicableStates = (rule.state || [])
|
||||
const statesSelectors = applicableStates.map(state => {
|
||||
const selector = states[state] || ''
|
||||
let arraySelector = Array.isArray(selector) ? selector : [selector]
|
||||
if (ignoreOutOfTreeSelector || liteMode) arraySelector = [arraySelector[0]]
|
||||
arraySelector
|
||||
.sort((a, b) => {
|
||||
if (a.startsWith(':')) return 1
|
||||
if (/^[a-z]/.exec(a)) return -1
|
||||
else return 0
|
||||
})
|
||||
.join('')
|
||||
return arraySelector
|
||||
})
|
||||
|
||||
const selectors = [realSelector, applicableVariant, ...applicableStates]
|
||||
.sort((a, b) => {
|
||||
if (a.startsWith(':')) return 1
|
||||
if (/^[a-z]/.exec(a)) return -1
|
||||
else return 0
|
||||
})
|
||||
.join('')
|
||||
const statesSelectorsFlat = statesSelectors.reduce((acc, s) => {
|
||||
return expand(acc, s).map(st => st.join(''))
|
||||
}, [])
|
||||
|
||||
const componentVariant = expand(componentSelectors, variantSelectors).map(cv => cv.join(''))
|
||||
const componentVariantStates = expand(componentVariant, statesSelectorsFlat).map(cvs => cvs.join(''))
|
||||
const selectors = expand(componentVariantStates, children).map(cvsc => cvsc.join(' '))
|
||||
/*
|
||||
*/
|
||||
|
||||
if (rule.parent) {
|
||||
return (genericRuleToSelector(components)(rule.parent, ignoreOutOfTreeSelector, true) + ' ' + selectors).trim()
|
||||
return genericRuleToSelector(components)(rule.parent, ignoreOutOfTreeSelector, liteMode, selectors)
|
||||
}
|
||||
return selectors.trim()
|
||||
|
||||
return selectors.join(', ').trim()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -354,10 +354,6 @@ export const convertTheme2To3 = (data) => {
|
|||
newRules.push({ ...rule, state: ['toggled'] })
|
||||
newRules.push({ ...rule, state: ['toggled', 'focus'] })
|
||||
newRules.push({ ...rule, state: ['pressed', 'focus'] })
|
||||
}
|
||||
if (key === 'buttonHover') {
|
||||
newRules.push({ ...rule, state: ['toggled', 'hover'] })
|
||||
newRules.push({ ...rule, state: ['pressed', 'hover'] })
|
||||
newRules.push({ ...rule, state: ['toggled', 'focus', 'hover'] })
|
||||
newRules.push({ ...rule, state: ['pressed', 'focus', 'hover'] })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { alphaBlend, getTextColor, relativeLuminance } from '../color_convert/co
|
|||
|
||||
export const process = (text, functions, { findColor, findShadow }, { dynamicVars, staticVars }) => {
|
||||
const { funcName, argsString } = /\$(?<funcName>\w+)\((?<argsString>[#a-zA-Z0-9-,.'"\s]*)\)/.exec(text).groups
|
||||
const args = argsString.split(/,/g).map(a => a.trim())
|
||||
const args = argsString.split(/ /g).map(a => a.trim())
|
||||
|
||||
const func = functions[funcName]
|
||||
if (args.length < func.argsNeeded) {
|
||||
|
|
@ -15,6 +15,11 @@ export const process = (text, functions, { findColor, findShadow }, { dynamicVar
|
|||
export const colorFunctions = {
|
||||
alpha: {
|
||||
argsNeeded: 2,
|
||||
documentation: 'Changes alpha value of the color only to be used for CSS variables',
|
||||
args: [
|
||||
'color: source color used',
|
||||
'amount: alpha value'
|
||||
],
|
||||
exec: (args, { findColor }, { dynamicVars, staticVars }) => {
|
||||
const [color, amountArg] = args
|
||||
|
||||
|
|
@ -23,8 +28,32 @@ export const colorFunctions = {
|
|||
return { ...colorArg, a: amount }
|
||||
}
|
||||
},
|
||||
brightness: {
|
||||
argsNeeded: 2,
|
||||
document: 'Changes brightness/lightness of color in HSL colorspace',
|
||||
args: [
|
||||
'color: source color used',
|
||||
'amount: lightness value'
|
||||
],
|
||||
exec: (args, { findColor }, { dynamicVars, staticVars }) => {
|
||||
const [color, amountArg] = args
|
||||
|
||||
const colorArg = convert(findColor(color, { dynamicVars, staticVars })).hsl
|
||||
colorArg.l += Number(amountArg)
|
||||
return { ...convert(colorArg).rgb }
|
||||
}
|
||||
},
|
||||
textColor: {
|
||||
argsNeeded: 2,
|
||||
documentation: 'Get text color with adequate contrast for given background and intended text color. Same function is used internally',
|
||||
args: [
|
||||
'background: color of backdrop where text will be shown',
|
||||
'foreground: intended text color',
|
||||
`[preserve]: (optional) intended color preservation:
|
||||
'preserve' - try to preserve the color
|
||||
'no-preserve' - if can't get adequate color - fall back to black or white
|
||||
'no-auto' - don't do anything (useless as a color function)`
|
||||
],
|
||||
exec: (args, { findColor }, { dynamicVars, staticVars }) => {
|
||||
const [backgroundArg, foregroundArg, preserve = 'preserve'] = args
|
||||
|
||||
|
|
@ -36,6 +65,12 @@ export const colorFunctions = {
|
|||
},
|
||||
blend: {
|
||||
argsNeeded: 3,
|
||||
documentation: 'Alpha blending between two colors',
|
||||
args: [
|
||||
'background: bottom layer color',
|
||||
'amount: opacity of top layer',
|
||||
'foreground: upper layer color'
|
||||
],
|
||||
exec: (args, { findColor }, { dynamicVars, staticVars }) => {
|
||||
const [backgroundArg, amountArg, foregroundArg] = args
|
||||
|
||||
|
|
@ -48,6 +83,11 @@ export const colorFunctions = {
|
|||
},
|
||||
mod: {
|
||||
argsNeeded: 2,
|
||||
documentation: 'Old function that increases or decreases brightness depending if color is dark or light. Advised against using it as it might give unexpected results.',
|
||||
args: [
|
||||
'color: source color',
|
||||
'amount: how much darken/brighten the color'
|
||||
],
|
||||
exec: (args, { findColor }, { dynamicVars, staticVars }) => {
|
||||
const [colorArg, amountArg] = args
|
||||
|
||||
|
|
@ -65,6 +105,13 @@ export const colorFunctions = {
|
|||
export const shadowFunctions = {
|
||||
borderSide: {
|
||||
argsNeeded: 3,
|
||||
documentation: 'Simulate a border on a side with a shadow, best works on inset border',
|
||||
args: [
|
||||
'color: border color',
|
||||
'side: string indicating on which side border should be, takes either one word or two words joined by dash (i.e. "left" or "bottom-right")',
|
||||
'[alpha]: (Optional) border opacity, defaults to 1 (fully opaque)',
|
||||
'[inset]: (Optional) whether border should be on the inside or outside, defaults to inside'
|
||||
],
|
||||
exec: (args, { findColor }) => {
|
||||
const [color, side, alpha = '1', widthArg = '1', inset = 'inset'] = args
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import {
|
|||
normalizeCombination,
|
||||
findRules
|
||||
} from './iss_utils.js'
|
||||
import { parseCssShadow } from './css_utils.js'
|
||||
import { deserializeShadow } from './iss_deserializer.js'
|
||||
|
||||
// Ensuring the order of components
|
||||
const components = {
|
||||
|
|
@ -37,18 +37,18 @@ const components = {
|
|||
ChatMessage: null
|
||||
}
|
||||
|
||||
const findShadow = (shadows, { dynamicVars, staticVars }) => {
|
||||
export const findShadow = (shadows, { dynamicVars, staticVars }) => {
|
||||
return (shadows || []).map(shadow => {
|
||||
let targetShadow
|
||||
if (typeof shadow === 'string') {
|
||||
if (shadow.startsWith('$')) {
|
||||
targetShadow = process(shadow, shadowFunctions, { findColor, findShadow }, { dynamicVars, staticVars })
|
||||
} else if (shadow.startsWith('--')) {
|
||||
const [variable] = shadow.split(/,/g).map(str => str.trim()) // discarding modifier since it's not supported
|
||||
const variableSlot = variable.substring(2)
|
||||
// modifiers are completely unsupported here
|
||||
const variableSlot = shadow.substring(2)
|
||||
return findShadow(staticVars[variableSlot], { dynamicVars, staticVars })
|
||||
} else {
|
||||
targetShadow = parseCssShadow(shadow)
|
||||
targetShadow = deserializeShadow(shadow)
|
||||
}
|
||||
} else {
|
||||
targetShadow = shadow
|
||||
|
|
@ -62,54 +62,63 @@ const findShadow = (shadows, { dynamicVars, staticVars }) => {
|
|||
})
|
||||
}
|
||||
|
||||
const findColor = (color, { dynamicVars, staticVars }) => {
|
||||
if (typeof color !== 'string' || (!color.startsWith('--') && !color.startsWith('$'))) return color
|
||||
let targetColor = null
|
||||
if (color.startsWith('--')) {
|
||||
const [variable, modifier] = color.split(/,/g).map(str => str.trim())
|
||||
const variableSlot = variable.substring(2)
|
||||
if (variableSlot === 'stack') {
|
||||
const { r, g, b } = dynamicVars.stacked
|
||||
targetColor = { r, g, b }
|
||||
} else if (variableSlot.startsWith('parent')) {
|
||||
if (variableSlot === 'parent') {
|
||||
const { r, g, b } = dynamicVars.lowerLevelBackground
|
||||
export const findColor = (color, { dynamicVars, staticVars }) => {
|
||||
try {
|
||||
if (typeof color !== 'string' || (!color.startsWith('--') && !color.startsWith('$'))) return color
|
||||
let targetColor = null
|
||||
if (color.startsWith('--')) {
|
||||
// Modifier support is pretty much for v2 themes only
|
||||
const [variable, modifier] = color.split(/,/g).map(str => str.trim())
|
||||
const variableSlot = variable.substring(2)
|
||||
if (variableSlot === 'stack') {
|
||||
const { r, g, b } = dynamicVars.stacked
|
||||
targetColor = { r, g, b }
|
||||
} else if (variableSlot.startsWith('parent')) {
|
||||
if (variableSlot === 'parent') {
|
||||
const { r, g, b } = dynamicVars.lowerLevelBackground
|
||||
targetColor = { r, g, b }
|
||||
} else {
|
||||
const virtualSlot = variableSlot.replace(/^parent/, '')
|
||||
targetColor = convert(dynamicVars.lowerLevelVirtualDirectivesRaw[virtualSlot]).rgb
|
||||
}
|
||||
} else {
|
||||
const virtualSlot = variableSlot.replace(/^parent/, '')
|
||||
targetColor = convert(dynamicVars.lowerLevelVirtualDirectivesRaw[virtualSlot]).rgb
|
||||
switch (variableSlot) {
|
||||
case 'inheritedBackground':
|
||||
targetColor = convert(dynamicVars.inheritedBackground).rgb
|
||||
break
|
||||
case 'background':
|
||||
targetColor = convert(dynamicVars.background).rgb
|
||||
break
|
||||
default:
|
||||
targetColor = convert(staticVars[variableSlot]).rgb
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (variableSlot) {
|
||||
case 'inheritedBackground':
|
||||
targetColor = convert(dynamicVars.inheritedBackground).rgb
|
||||
break
|
||||
case 'background':
|
||||
targetColor = convert(dynamicVars.background).rgb
|
||||
break
|
||||
default:
|
||||
targetColor = convert(staticVars[variableSlot]).rgb
|
||||
|
||||
if (modifier) {
|
||||
const effectiveBackground = dynamicVars.lowerLevelBackground ?? targetColor
|
||||
const isLightOnDark = relativeLuminance(convert(effectiveBackground).rgb) < 0.5
|
||||
const mod = isLightOnDark ? 1 : -1
|
||||
targetColor = brightness(Number.parseFloat(modifier) * mod, targetColor).rgb
|
||||
}
|
||||
}
|
||||
|
||||
if (modifier) {
|
||||
const effectiveBackground = dynamicVars.lowerLevelBackground ?? targetColor
|
||||
const isLightOnDark = relativeLuminance(convert(effectiveBackground).rgb) < 0.5
|
||||
const mod = isLightOnDark ? 1 : -1
|
||||
targetColor = brightness(Number.parseFloat(modifier) * mod, targetColor).rgb
|
||||
if (color.startsWith('$')) {
|
||||
try {
|
||||
targetColor = process(color, colorFunctions, { findColor }, { dynamicVars, staticVars })
|
||||
} catch (e) {
|
||||
console.error('Failure executing color function', e)
|
||||
targetColor = '#FF00FF'
|
||||
}
|
||||
}
|
||||
// Color references other color
|
||||
return targetColor
|
||||
} catch (e) {
|
||||
throw new Error(`Couldn't find color "${color}", variables are:
|
||||
Static:
|
||||
${JSON.stringify(staticVars, null, 2)}
|
||||
Dynamic:
|
||||
${JSON.stringify(dynamicVars, null, 2)}`)
|
||||
}
|
||||
|
||||
if (color.startsWith('$')) {
|
||||
try {
|
||||
targetColor = process(color, colorFunctions, { findColor }, { dynamicVars, staticVars })
|
||||
} catch (e) {
|
||||
console.error('Failure executing color function', e)
|
||||
targetColor = '#FF00FF'
|
||||
}
|
||||
}
|
||||
// Color references other color
|
||||
return targetColor
|
||||
}
|
||||
|
||||
const getTextColorAlpha = (directives, intendedTextColor, dynamicVars, staticVars) => {
|
||||
|
|
@ -164,19 +173,19 @@ export const getEngineChecksum = () => engineChecksum
|
|||
* @param {boolean} onlyNormalState - only use components 'normal' states, meant for generating theme
|
||||
* previews since states are the biggest factor for compilation time and are completely unnecessary
|
||||
* when previewing multiple themes at same time
|
||||
* @param {string} rootComponentName - [UNTESTED] which component to start from, meant for previewing a
|
||||
* part of the theme (i.e. just the button) for themes 3 editor.
|
||||
*/
|
||||
export const init = ({
|
||||
inputRuleset,
|
||||
ultimateBackgroundColor,
|
||||
debug = false,
|
||||
liteMode = false,
|
||||
editMode = false,
|
||||
onlyNormalState = false,
|
||||
rootComponentName = 'Root'
|
||||
initialStaticVars = {}
|
||||
}) => {
|
||||
const rootComponentName = 'Root'
|
||||
if (!inputRuleset) throw new Error('Ruleset is null or undefined!')
|
||||
const staticVars = {}
|
||||
const staticVars = { ...initialStaticVars }
|
||||
const stacked = {}
|
||||
const computed = {}
|
||||
|
||||
|
|
@ -218,8 +227,8 @@ export const init = ({
|
|||
bScore += b.component === 'Text' ? 1 : 0
|
||||
|
||||
// Debug
|
||||
a.specifityScore = aScore
|
||||
b.specifityScore = bScore
|
||||
a._specificityScore = aScore
|
||||
b._specificityScore = bScore
|
||||
|
||||
if (aScore === bScore) {
|
||||
return ai - bi
|
||||
|
|
@ -228,211 +237,227 @@ export const init = ({
|
|||
})
|
||||
.map(({ data }) => data)
|
||||
|
||||
if (!ultimateBackgroundColor) {
|
||||
console.warn('No ultimate background color provided, falling back to panel color')
|
||||
const rootRule = ruleset.findLast((x) => (x.component === 'Root' && x.directives?.['--bg']))
|
||||
ultimateBackgroundColor = rootRule.directives['--bg'].split('|')[1].trim()
|
||||
}
|
||||
|
||||
const virtualComponents = new Set(Object.values(components).filter(c => c.virtual).map(c => c.name))
|
||||
const nonEditableComponents = new Set(Object.values(components).filter(c => c.notEditable).map(c => c.name))
|
||||
|
||||
const processCombination = (combination) => {
|
||||
const selector = ruleToSelector(combination, true)
|
||||
const cssSelector = ruleToSelector(combination)
|
||||
try {
|
||||
const selector = ruleToSelector(combination, true)
|
||||
const cssSelector = ruleToSelector(combination)
|
||||
|
||||
const parentSelector = selector.split(/ /g).slice(0, -1).join(' ')
|
||||
const soloSelector = selector.split(/ /g).slice(-1)[0]
|
||||
const parentSelector = selector.split(/ /g).slice(0, -1).join(' ')
|
||||
const soloSelector = selector.split(/ /g).slice(-1)[0]
|
||||
|
||||
const lowerLevelSelector = parentSelector
|
||||
const lowerLevelBackground = computed[lowerLevelSelector]?.background
|
||||
const lowerLevelVirtualDirectives = computed[lowerLevelSelector]?.virtualDirectives
|
||||
const lowerLevelVirtualDirectivesRaw = computed[lowerLevelSelector]?.virtualDirectivesRaw
|
||||
const lowerLevelSelector = parentSelector
|
||||
let lowerLevelBackground = computed[lowerLevelSelector]?.background
|
||||
if (editMode && !lowerLevelBackground) {
|
||||
// FIXME hack for editor until it supports handling component backgrounds
|
||||
lowerLevelBackground = '#00FFFF'
|
||||
}
|
||||
const lowerLevelVirtualDirectives = computed[lowerLevelSelector]?.virtualDirectives
|
||||
const lowerLevelVirtualDirectivesRaw = computed[lowerLevelSelector]?.virtualDirectivesRaw
|
||||
|
||||
const dynamicVars = computed[selector] || {
|
||||
lowerLevelBackground,
|
||||
lowerLevelVirtualDirectives,
|
||||
lowerLevelVirtualDirectivesRaw
|
||||
}
|
||||
|
||||
// Inheriting all of the applicable rules
|
||||
const existingRules = ruleset.filter(findRules(combination))
|
||||
const computedDirectives =
|
||||
existingRules
|
||||
.map(r => r.directives)
|
||||
.reduce((acc, directives) => ({ ...acc, ...directives }), {})
|
||||
const computedRule = {
|
||||
...combination,
|
||||
directives: computedDirectives
|
||||
}
|
||||
|
||||
computed[selector] = computed[selector] || {}
|
||||
computed[selector].computedRule = computedRule
|
||||
computed[selector].dynamicVars = dynamicVars
|
||||
|
||||
if (virtualComponents.has(combination.component)) {
|
||||
const virtualName = [
|
||||
'--',
|
||||
combination.component.toLowerCase(),
|
||||
combination.variant === 'normal'
|
||||
? ''
|
||||
: combination.variant[0].toUpperCase() + combination.variant.slice(1).toLowerCase(),
|
||||
...sortBy(combination.state.filter(x => x !== 'normal')).map(state => state[0].toUpperCase() + state.slice(1).toLowerCase())
|
||||
].join('')
|
||||
|
||||
let inheritedTextColor = computedDirectives.textColor
|
||||
let inheritedTextAuto = computedDirectives.textAuto
|
||||
let inheritedTextOpacity = computedDirectives.textOpacity
|
||||
let inheritedTextOpacityMode = computedDirectives.textOpacityMode
|
||||
const lowerLevelTextSelector = [...selector.split(/ /g).slice(0, -1), soloSelector].join(' ')
|
||||
const lowerLevelTextRule = computed[lowerLevelTextSelector]
|
||||
|
||||
if (inheritedTextColor == null || inheritedTextOpacity == null || inheritedTextOpacityMode == null) {
|
||||
inheritedTextColor = computedDirectives.textColor ?? lowerLevelTextRule.textColor
|
||||
inheritedTextAuto = computedDirectives.textAuto ?? lowerLevelTextRule.textAuto
|
||||
inheritedTextOpacity = computedDirectives.textOpacity ?? lowerLevelTextRule.textOpacity
|
||||
inheritedTextOpacityMode = computedDirectives.textOpacityMode ?? lowerLevelTextRule.textOpacityMode
|
||||
const dynamicVars = computed[selector] || {
|
||||
lowerLevelBackground,
|
||||
lowerLevelVirtualDirectives,
|
||||
lowerLevelVirtualDirectivesRaw
|
||||
}
|
||||
|
||||
const newTextRule = {
|
||||
...computedRule,
|
||||
directives: {
|
||||
...computedRule.directives,
|
||||
textColor: inheritedTextColor,
|
||||
textAuto: inheritedTextAuto ?? 'preserve',
|
||||
textOpacity: inheritedTextOpacity,
|
||||
textOpacityMode: inheritedTextOpacityMode
|
||||
}
|
||||
}
|
||||
|
||||
dynamicVars.inheritedBackground = lowerLevelBackground
|
||||
dynamicVars.stacked = convert(stacked[lowerLevelSelector]).rgb
|
||||
|
||||
const intendedTextColor = convert(findColor(inheritedTextColor, { dynamicVars, staticVars })).rgb
|
||||
const textColor = newTextRule.directives.textAuto === 'no-auto'
|
||||
? intendedTextColor
|
||||
: getTextColor(
|
||||
convert(stacked[lowerLevelSelector]).rgb,
|
||||
intendedTextColor,
|
||||
newTextRule.directives.textAuto === 'preserve'
|
||||
)
|
||||
const virtualDirectives = computed[lowerLevelSelector].virtualDirectives || {}
|
||||
const virtualDirectivesRaw = computed[lowerLevelSelector].virtualDirectivesRaw || {}
|
||||
|
||||
// Storing color data in lower layer to use as custom css properties
|
||||
virtualDirectives[virtualName] = getTextColorAlpha(newTextRule.directives, textColor, dynamicVars)
|
||||
virtualDirectivesRaw[virtualName] = textColor
|
||||
|
||||
computed[lowerLevelSelector].virtualDirectives = virtualDirectives
|
||||
computed[lowerLevelSelector].virtualDirectivesRaw = virtualDirectivesRaw
|
||||
|
||||
return {
|
||||
dynamicVars,
|
||||
selector: cssSelector.split(/ /g).slice(0, -1).join(' '),
|
||||
...combination,
|
||||
directives: {},
|
||||
virtualDirectives: {
|
||||
[virtualName]: getTextColorAlpha(newTextRule.directives, textColor, dynamicVars)
|
||||
},
|
||||
virtualDirectivesRaw: {
|
||||
[virtualName]: textColor
|
||||
}
|
||||
}
|
||||
} else {
|
||||
computed[selector] = computed[selector] || {}
|
||||
|
||||
// TODO: DEFAULT TEXT COLOR
|
||||
const lowerLevelStackedBackground = stacked[lowerLevelSelector] || convert(ultimateBackgroundColor).rgb
|
||||
|
||||
if (computedDirectives.background) {
|
||||
let inheritRule = null
|
||||
const variantRules = ruleset.filter(
|
||||
findRules({
|
||||
component: combination.component,
|
||||
variant: combination.variant,
|
||||
parent: combination.parent
|
||||
})
|
||||
)
|
||||
const lastVariantRule = variantRules[variantRules.length - 1]
|
||||
if (lastVariantRule) {
|
||||
inheritRule = lastVariantRule
|
||||
} else {
|
||||
const normalRules = ruleset.filter(findRules({
|
||||
component: combination.component,
|
||||
parent: combination.parent
|
||||
}))
|
||||
const lastNormalRule = normalRules[normalRules.length - 1]
|
||||
inheritRule = lastNormalRule
|
||||
}
|
||||
|
||||
const inheritSelector = ruleToSelector({ ...inheritRule, parent: combination.parent }, true)
|
||||
const inheritedBackground = computed[inheritSelector].background
|
||||
|
||||
dynamicVars.inheritedBackground = inheritedBackground
|
||||
|
||||
const rgb = convert(findColor(computedDirectives.background, { dynamicVars, staticVars })).rgb
|
||||
|
||||
if (!stacked[selector]) {
|
||||
let blend
|
||||
const alpha = computedDirectives.opacity ?? 1
|
||||
if (alpha >= 1) {
|
||||
blend = rgb
|
||||
} else if (alpha <= 0) {
|
||||
blend = lowerLevelStackedBackground
|
||||
} else {
|
||||
blend = alphaBlend(rgb, computedDirectives.opacity, lowerLevelStackedBackground)
|
||||
}
|
||||
stacked[selector] = blend
|
||||
computed[selector].background = { ...rgb, a: computedDirectives.opacity ?? 1 }
|
||||
}
|
||||
}
|
||||
|
||||
if (computedDirectives.shadow) {
|
||||
dynamicVars.shadow = flattenDeep(findShadow(flattenDeep(computedDirectives.shadow), { dynamicVars, staticVars }))
|
||||
}
|
||||
|
||||
if (!stacked[selector]) {
|
||||
computedDirectives.background = 'transparent'
|
||||
computedDirectives.opacity = 0
|
||||
stacked[selector] = lowerLevelStackedBackground
|
||||
computed[selector].background = { ...lowerLevelStackedBackground, a: 0 }
|
||||
}
|
||||
|
||||
dynamicVars.stacked = stacked[selector]
|
||||
dynamicVars.background = computed[selector].background
|
||||
|
||||
const dynamicSlots = Object.entries(computedDirectives).filter(([k, v]) => k.startsWith('--'))
|
||||
|
||||
dynamicSlots.forEach(([k, v]) => {
|
||||
const [type, ...value] = v.split('|').map(x => x.trim()) // woah, Extreme!
|
||||
switch (type) {
|
||||
case 'color': {
|
||||
const color = findColor(value[0], { dynamicVars, staticVars })
|
||||
dynamicVars[k] = color
|
||||
if (combination.component === 'Root') {
|
||||
staticVars[k.substring(2)] = color
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'shadow': {
|
||||
const shadow = value
|
||||
dynamicVars[k] = shadow
|
||||
if (combination.component === 'Root') {
|
||||
staticVars[k.substring(2)] = shadow
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'generic': {
|
||||
dynamicVars[k] = value
|
||||
if (combination.component === 'Root') {
|
||||
staticVars[k.substring(2)] = value
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const rule = {
|
||||
dynamicVars,
|
||||
selector: cssSelector,
|
||||
// Inheriting all of the applicable rules
|
||||
const existingRules = ruleset.filter(findRules(combination))
|
||||
const computedDirectives =
|
||||
existingRules
|
||||
.map(r => r.directives)
|
||||
.reduce((acc, directives) => ({ ...acc, ...directives }), {})
|
||||
const computedRule = {
|
||||
...combination,
|
||||
directives: computedDirectives
|
||||
}
|
||||
|
||||
return rule
|
||||
computed[selector] = computed[selector] || {}
|
||||
computed[selector].computedRule = computedRule
|
||||
computed[selector].dynamicVars = dynamicVars
|
||||
|
||||
if (virtualComponents.has(combination.component)) {
|
||||
const virtualName = [
|
||||
'--',
|
||||
combination.component.toLowerCase(),
|
||||
combination.variant === 'normal'
|
||||
? ''
|
||||
: combination.variant[0].toUpperCase() + combination.variant.slice(1).toLowerCase(),
|
||||
...sortBy(combination.state.filter(x => x !== 'normal')).map(state => state[0].toUpperCase() + state.slice(1).toLowerCase())
|
||||
].join('')
|
||||
|
||||
let inheritedTextColor = computedDirectives.textColor
|
||||
let inheritedTextAuto = computedDirectives.textAuto
|
||||
let inheritedTextOpacity = computedDirectives.textOpacity
|
||||
let inheritedTextOpacityMode = computedDirectives.textOpacityMode
|
||||
const lowerLevelTextSelector = [...selector.split(/ /g).slice(0, -1), soloSelector].join(' ')
|
||||
const lowerLevelTextRule = computed[lowerLevelTextSelector]
|
||||
|
||||
if (inheritedTextColor == null || inheritedTextOpacity == null || inheritedTextOpacityMode == null) {
|
||||
inheritedTextColor = computedDirectives.textColor ?? lowerLevelTextRule.textColor
|
||||
inheritedTextAuto = computedDirectives.textAuto ?? lowerLevelTextRule.textAuto
|
||||
inheritedTextOpacity = computedDirectives.textOpacity ?? lowerLevelTextRule.textOpacity
|
||||
inheritedTextOpacityMode = computedDirectives.textOpacityMode ?? lowerLevelTextRule.textOpacityMode
|
||||
}
|
||||
|
||||
const newTextRule = {
|
||||
...computedRule,
|
||||
directives: {
|
||||
...computedRule.directives,
|
||||
textColor: inheritedTextColor,
|
||||
textAuto: inheritedTextAuto ?? 'preserve',
|
||||
textOpacity: inheritedTextOpacity,
|
||||
textOpacityMode: inheritedTextOpacityMode
|
||||
}
|
||||
}
|
||||
|
||||
dynamicVars.inheritedBackground = lowerLevelBackground
|
||||
dynamicVars.stacked = convert(stacked[lowerLevelSelector]).rgb
|
||||
|
||||
const intendedTextColor = convert(findColor(inheritedTextColor, { dynamicVars, staticVars })).rgb
|
||||
const textColor = newTextRule.directives.textAuto === 'no-auto'
|
||||
? intendedTextColor
|
||||
: getTextColor(
|
||||
convert(stacked[lowerLevelSelector]).rgb,
|
||||
intendedTextColor,
|
||||
newTextRule.directives.textAuto === 'preserve'
|
||||
)
|
||||
const virtualDirectives = computed[lowerLevelSelector].virtualDirectives || {}
|
||||
const virtualDirectivesRaw = computed[lowerLevelSelector].virtualDirectivesRaw || {}
|
||||
|
||||
// Storing color data in lower layer to use as custom css properties
|
||||
virtualDirectives[virtualName] = getTextColorAlpha(newTextRule.directives, textColor, dynamicVars)
|
||||
virtualDirectivesRaw[virtualName] = textColor
|
||||
|
||||
computed[lowerLevelSelector].virtualDirectives = virtualDirectives
|
||||
computed[lowerLevelSelector].virtualDirectivesRaw = virtualDirectivesRaw
|
||||
|
||||
return {
|
||||
dynamicVars,
|
||||
selector: cssSelector.split(/ /g).slice(0, -1).join(' '),
|
||||
...combination,
|
||||
directives: {},
|
||||
virtualDirectives: {
|
||||
[virtualName]: getTextColorAlpha(newTextRule.directives, textColor, dynamicVars)
|
||||
},
|
||||
virtualDirectivesRaw: {
|
||||
[virtualName]: textColor
|
||||
}
|
||||
}
|
||||
} else {
|
||||
computed[selector] = computed[selector] || {}
|
||||
|
||||
// TODO: DEFAULT TEXT COLOR
|
||||
const lowerLevelStackedBackground = stacked[lowerLevelSelector] || convert(ultimateBackgroundColor).rgb
|
||||
|
||||
if (computedDirectives.background) {
|
||||
let inheritRule = null
|
||||
const variantRules = ruleset.filter(
|
||||
findRules({
|
||||
component: combination.component,
|
||||
variant: combination.variant,
|
||||
parent: combination.parent
|
||||
})
|
||||
)
|
||||
const lastVariantRule = variantRules[variantRules.length - 1]
|
||||
if (lastVariantRule) {
|
||||
inheritRule = lastVariantRule
|
||||
} else {
|
||||
const normalRules = ruleset.filter(findRules({
|
||||
component: combination.component,
|
||||
parent: combination.parent
|
||||
}))
|
||||
const lastNormalRule = normalRules[normalRules.length - 1]
|
||||
inheritRule = lastNormalRule
|
||||
}
|
||||
|
||||
const inheritSelector = ruleToSelector({ ...inheritRule, parent: combination.parent }, true)
|
||||
const inheritedBackground = computed[inheritSelector].background
|
||||
|
||||
dynamicVars.inheritedBackground = inheritedBackground
|
||||
|
||||
const rgb = convert(findColor(computedDirectives.background, { dynamicVars, staticVars })).rgb
|
||||
|
||||
if (!stacked[selector]) {
|
||||
let blend
|
||||
const alpha = computedDirectives.opacity ?? 1
|
||||
if (alpha >= 1) {
|
||||
blend = rgb
|
||||
} else if (alpha <= 0) {
|
||||
blend = lowerLevelStackedBackground
|
||||
} else {
|
||||
blend = alphaBlend(rgb, computedDirectives.opacity, lowerLevelStackedBackground)
|
||||
}
|
||||
stacked[selector] = blend
|
||||
computed[selector].background = { ...rgb, a: computedDirectives.opacity ?? 1 }
|
||||
}
|
||||
}
|
||||
|
||||
if (computedDirectives.shadow) {
|
||||
dynamicVars.shadow = flattenDeep(findShadow(flattenDeep(computedDirectives.shadow), { dynamicVars, staticVars }))
|
||||
}
|
||||
|
||||
if (!stacked[selector]) {
|
||||
computedDirectives.background = 'transparent'
|
||||
computedDirectives.opacity = 0
|
||||
stacked[selector] = lowerLevelStackedBackground
|
||||
computed[selector].background = { ...lowerLevelStackedBackground, a: 0 }
|
||||
}
|
||||
|
||||
dynamicVars.stacked = stacked[selector]
|
||||
dynamicVars.background = computed[selector].background
|
||||
|
||||
const dynamicSlots = Object.entries(computedDirectives).filter(([k, v]) => k.startsWith('--'))
|
||||
|
||||
dynamicSlots.forEach(([k, v]) => {
|
||||
const [type, value] = v.split('|').map(x => x.trim()) // woah, Extreme!
|
||||
switch (type) {
|
||||
case 'color': {
|
||||
const color = findColor(value, { dynamicVars, staticVars })
|
||||
dynamicVars[k] = color
|
||||
if (combination.component === rootComponentName) {
|
||||
staticVars[k.substring(2)] = color
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'shadow': {
|
||||
const shadow = value.split(/,/g).map(s => s.trim()).filter(x => x)
|
||||
dynamicVars[k] = shadow
|
||||
if (combination.component === rootComponentName) {
|
||||
staticVars[k.substring(2)] = shadow
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'generic': {
|
||||
dynamicVars[k] = value
|
||||
if (combination.component === rootComponentName) {
|
||||
staticVars[k.substring(2)] = value
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const rule = {
|
||||
dynamicVars,
|
||||
selector: cssSelector,
|
||||
...combination,
|
||||
directives: computedDirectives
|
||||
}
|
||||
|
||||
return rule
|
||||
}
|
||||
} catch (e) {
|
||||
const { component, variant, state } = combination
|
||||
throw new Error(`Error processing combination ${component}.${variant}:${state.join(':')}: ${e}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -443,11 +468,15 @@ export const init = ({
|
|||
variants: originalVariants = {}
|
||||
} = component
|
||||
|
||||
const validInnerComponents = (
|
||||
liteMode
|
||||
? (component.validInnerComponentsLite || component.validInnerComponents)
|
||||
: component.validInnerComponents
|
||||
) || []
|
||||
let validInnerComponents
|
||||
if (editMode) {
|
||||
const temp = (component.validInnerComponentsLite || component.validInnerComponents || [])
|
||||
validInnerComponents = temp.filter(c => virtualComponents.has(c) && !nonEditableComponents.has(c))
|
||||
} else if (liteMode) {
|
||||
validInnerComponents = (component.validInnerComponentsLite || component.validInnerComponents || [])
|
||||
} else {
|
||||
validInnerComponents = component.validInnerComponents || []
|
||||
}
|
||||
|
||||
// Normalizing states and variants to always include "normal"
|
||||
const states = { normal: '', ...originalStates }
|
||||
|
|
@ -489,7 +518,7 @@ export const init = ({
|
|||
combination.component = component.name
|
||||
combination.lazy = component.lazy || parent?.lazy
|
||||
combination.parent = parent
|
||||
if (combination.state.indexOf('hover') >= 0) {
|
||||
if (!liteMode && combination.state.indexOf('hover') >= 0) {
|
||||
combination.lazy = true
|
||||
}
|
||||
|
||||
|
|
@ -538,6 +567,7 @@ export const init = ({
|
|||
lazy,
|
||||
eager,
|
||||
staticVars,
|
||||
engineChecksum
|
||||
engineChecksum,
|
||||
themeChecksum: sum([lazy, eager])
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue