diff options
Diffstat (limited to 'src/services')
| -rw-r--r-- | src/services/style_setter/style_setter.js | 89 | ||||
| -rw-r--r-- | src/services/theme_data/css_utils.js | 24 | ||||
| -rw-r--r-- | src/services/theme_data/iss_utils.js | 39 | ||||
| -rw-r--r-- | src/services/theme_data/theme2_to_theme3.js | 25 | ||||
| -rw-r--r-- | src/services/theme_data/theme_data.service.js | 1 | ||||
| -rw-r--r-- | src/services/theme_data/theme_data_3.service.js | 85 |
6 files changed, 195 insertions, 68 deletions
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js index 83faa0b3..e54a95bf 100644 --- a/src/services/style_setter/style_setter.js +++ b/src/services/style_setter/style_setter.js @@ -1,7 +1,5 @@ import { hex2rgb } from '../color_convert/color_convert.js' -import { generatePreset } from '../theme_data/theme_data.service.js' import { init, getEngineChecksum } from '../theme_data/theme_data_3.service.js' -import { convertTheme2To3 } from '../theme_data/theme2_to_theme3.js' import { getCssRules } from '../theme_data/css_utils.js' import { defaultState } from '../../modules/config.js' import { chunk } from 'lodash' @@ -45,25 +43,21 @@ const adoptStyleSheets = (styles) => { // is nothing to do here. } -export const generateTheme = async (input, callbacks) => { +export const generateTheme = async (inputRuleset, callbacks, debug) => { const { onNewRule = (rule, isLazy) => {}, onLazyFinished = () => {}, onEagerFinished = () => {} } = callbacks - let extraRules - if (input.themeFileVersion === 1) { - extraRules = convertTheme2To3(input) - } else { - const { theme } = generatePreset(input) - extraRules = convertTheme2To3(theme) - } - // Assuming that "worst case scenario background" is panel background since it's the most likely one - const themes3 = init(extraRules, extraRules[0].directives['--bg'].split('|')[1].trim()) + const themes3 = init({ + inputRuleset, + ultimateBackgroundColor: inputRuleset[0].directives['--bg'].split('|')[1].trim(), + debug + }) - getCssRules(themes3.eager, themes3.staticVars).forEach(rule => { + 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) @@ -93,7 +87,7 @@ export const generateTheme = async (input, callbacks) => { const processChunk = () => { const chunk = chunks[counter] Promise.all(chunk.map(x => x())).then(result => { - getCssRules(result.filter(x => x), themes3.staticVars).forEach(rule => { + getCssRules(result.filter(x => x), debug).forEach(rule => { if (rule.match(/\.modal-view/)) { const parts = rule.split(/[{}]/g) const newRule = [ @@ -152,7 +146,7 @@ export const tryLoadCache = () => { } } -export const applyTheme = async (input, onFinish = (data) => {}) => { +export const applyTheme = async (input, onFinish = (data) => {}, debug) => { const eagerStyles = createStyleSheet(EAGER_STYLE_ID) const lazyStyles = createStyleSheet(LAZY_STYLE_ID) @@ -177,7 +171,8 @@ export const applyTheme = async (input, onFinish = (data) => {}) => { onFinish(cache) localStorage.setItem('pleroma-fe-theme-cache', JSON.stringify(cache)) } - } + }, + debug ) setTimeout(lazyProcessFunc, 0) @@ -185,15 +180,52 @@ export const applyTheme = async (input, onFinish = (data) => {}) => { return Promise.resolve() } -const configColumns = ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth, emojiReactionsScale }) => - ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth, emojiReactionsScale }) +const extractStyleConfig = ({ + sidebarColumnWidth, + contentColumnWidth, + notifsColumnWidth, + emojiReactionsScale, + emojiSize, + navbarSize, + panelHeaderSize, + textSize, + forcedRoundness +}) => { + const result = { + sidebarColumnWidth, + contentColumnWidth, + notifsColumnWidth, + emojiReactionsScale, + emojiSize, + navbarSize, + panelHeaderSize, + textSize + } + + switch (forcedRoundness) { + case 'disable': + break + case '0': + result.forcedRoundness = '0' + break + case '1': + result.forcedRoundness = '1px' + break + case '2': + result.forcedRoundness = '0.4rem' + break + default: + } + + return result +} -const defaultConfigColumns = configColumns(defaultState) +const defaultStyleConfig = extractStyleConfig(defaultState) -export const applyConfig = (config) => { - const columns = configColumns(config) +export const applyConfig = (input) => { + const config = extractStyleConfig(input) - if (columns === defaultConfigColumns) { + if (config === defaultStyleConfig) { return } @@ -202,16 +234,25 @@ export const applyConfig = (config) => { body.classList.add('hidden') const rules = Object - .entries(columns) + .entries(config) .filter(([k, v]) => v) .map(([k, v]) => `--${k}: ${v}`).join(';') + document.getElementById('style-config')?.remove() const styleEl = document.createElement('style') + styleEl.id = 'style-config' head.appendChild(styleEl) const styleSheet = styleEl.sheet styleSheet.toString() styleSheet.insertRule(`:root { ${rules} }`, 'index-max') + + if (Object.prototype.hasOwnProperty.call(config, 'forcedRoundness')) { + styleSheet.insertRule(` * { + --roundness: var(--forcedRoundness) !important; + }`, 'index-max') + } + body.classList.remove('hidden') } @@ -269,5 +310,3 @@ export const getPreset = (val) => { return { theme: data, source: theme.source } }) } - -export const setPreset = (val) => getPreset(val).then(data => applyTheme(data)) diff --git a/src/services/theme_data/css_utils.js b/src/services/theme_data/css_utils.js index a89eac3b..9bce4834 100644 --- a/src/services/theme_data/css_utils.js +++ b/src/services/theme_data/css_utils.js @@ -2,11 +2,6 @@ import { convert } from 'chromatism' import { hex2rgb, rgba2css } from '../color_convert/color_convert.js' -// This changes what backgrounds are used to "stacked" solid colors so you can see -// what theme engine "thinks" is actual background color is for purposes of text color -// generation and for when --stacked variable is used -const DEBUG = false - export const parseCssShadow = (text) => { const dimensions = /(\d[a-z]*\s?){2,4}/.exec(text)?.[0] const inset = /inset/.exec(text)?.[0] @@ -66,7 +61,10 @@ export const getCssShadowFilter = (input) => { .join(' ') } -export const getCssRules = (rules) => rules.map(rule => { +// `debug` changes what backgrounds are used to "stacked" solid colors so you can see +// what theme engine "thinks" is actual background color is for purposes of text color +// generation and for when --stacked variable is used +export const getCssRules = (rules, debug) => rules.map(rule => { let selector = rule.selector if (!selector) { selector = 'html' @@ -93,7 +91,7 @@ export const getCssRules = (rules) => rules.map(rule => { ].join(';\n ') } case 'background': { - if (DEBUG) { + if (debug) { return ` --background: ${getCssColorString(rule.dynamicVars.stacked)}; background-color: ${getCssColorString(rule.dynamicVars.stacked)}; @@ -161,3 +159,15 @@ export const getCssRules = (rules) => rules.map(rule => { footer ].join('\n') }).filter(x => x) + +export const getScopedVersion = (rules, newScope) => { + return rules.map(x => { + if (x.startsWith('html')) { + return x.replace('html', newScope) + } else if (x.startsWith('#content')) { + return x.replace('#content', newScope) + } else { + return newScope + ' > ' + x + } + }) +} diff --git a/src/services/theme_data/iss_utils.js b/src/services/theme_data/iss_utils.js index 83ca8242..75f8dd93 100644 --- a/src/services/theme_data/iss_utils.js +++ b/src/services/theme_data/iss_utils.js @@ -39,7 +39,23 @@ export const getAllPossibleCombinations = (array) => { return combos.reduce((acc, x) => [...acc, ...x], []) } -// Converts rule, parents and their criteria into a CSS (or path if ignoreOutOfTreeSelector == true) selector +/** + * Converts rule, parents and their criteria into a CSS (or path if ignoreOutOfTreeSelector == true) + * selector. + * + * "path" here refers to "fake" selector that cannot be actually used in UI but is used for internal + * purposes + * + * @param {Object} components - object containing all components definitions + * + * @returns {Function} + * @param {Object} rule - rule in question to convert to CSS selector + * @param {boolean} ignoreOutOfTreeSelector - wthether to ignore aformentioned field in + * component definition and use selector + * @param {boolean} isParent - (mostly) internal argument used when recursing + * + * @returns {String} CSS selector (or path) + */ export const genericRuleToSelector = components => (rule, ignoreOutOfTreeSelector, isParent) => { if (!rule && !isParent) return null const component = components[rule.component] @@ -79,6 +95,17 @@ export const genericRuleToSelector = components => (rule, ignoreOutOfTreeSelecto return selectors.trim() } +/** + * Check if combination matches + * + * @param {Object} criteria - criteria to match against + * @param {Object} subject - rule/combination to check match + * @param {boolean} strict - strict checking: + * By default every variant and state inherits from "normal" state/variant + * so when checking if combination matches, it WILL match against "normal" + * state/variant. In strict mode inheritance is ignored an "normal" does + * not match + */ export const combinationsMatch = (criteria, subject, strict) => { if (criteria.component !== subject.component) return false @@ -101,6 +128,15 @@ export const combinationsMatch = (criteria, subject, strict) => { return true } +/** + * Search for rule that matches `criteria` in set of rules + * meant to be used in a ruleset.filter() function + * + * @param {Object} criteria - criteria to search for + * @param {boolean} strict - whether search strictly or not (see combinationsMatch) + * + * @return function that returns true/false if subject matches + */ export const findRules = (criteria, strict) => subject => { // If we searching for "general" rules - ignore "specific" ones if (criteria.parent === null && !!subject.parent) return false @@ -125,6 +161,7 @@ export const findRules = (criteria, strict) => subject => { return true } +// Pre-fills 'normal' state/variant if missing export const normalizeCombination = rule => { rule.variant = rule.variant ?? 'normal' rule.state = [...new Set(['normal', ...(rule.state || [])])] diff --git a/src/services/theme_data/theme2_to_theme3.js b/src/services/theme_data/theme2_to_theme3.js index 6ea12836..95eb03c1 100644 --- a/src/services/theme_data/theme2_to_theme3.js +++ b/src/services/theme_data/theme2_to_theme3.js @@ -12,7 +12,9 @@ export const basePaletteKeys = new Set([ 'cBlue', 'cRed', 'cGreen', - 'cOrange' + 'cOrange', + + 'wallpaper' ]) export const fontsKeys = new Set([ @@ -138,7 +140,7 @@ export const convertTheme2To3 = (data) => { Object.keys(data.opacity || {}).forEach(key => { if (!opacityKeys.has(key) || data.opacity[key] === undefined) return null const originalOpacity = data.opacity[key] - const rule = {} + const rule = { source: '2to3' } switch (key) { case 'alert': @@ -213,7 +215,7 @@ export const convertTheme2To3 = (data) => { Object.keys(data.radii || {}).forEach(key => { if (!radiiKeys.has(key) || data.radii[key] === undefined) return null const originalRadius = data.radii[key] - const rule = {} + const rule = { source: '2to3' } switch (key) { case 'btn': @@ -265,8 +267,9 @@ export const convertTheme2To3 = (data) => { const newRules = [] Object.keys(data.fonts || {}).forEach(key => { if (!fontsKeys.has(key)) return + if (!data.fonts[key]) return const originalFont = data.fonts[key].family - const rule = {} + const rule = { source: '2to3' } switch (key) { case 'interface': @@ -300,7 +303,7 @@ export const convertTheme2To3 = (data) => { Object.keys(data.shadows || {}).forEach(key => { if (!shadowsKeys.has(key)) return const originalShadow = data.shadows[key] - const rule = {} + const rule = { source: '2to3' } switch (key) { case 'panel': @@ -369,7 +372,7 @@ export const convertTheme2To3 = (data) => { const extendedRules = Object.entries(extendedBaseKeys).map(([prefix, keys]) => { if (nonComponentPrefixes.has(prefix)) return null - const rule = {} + const rule = { source: '2to3' } if (prefix === 'alertPopup') { rule.component = 'Alert' rule.parent = { component: 'Popover' } @@ -402,7 +405,7 @@ export const convertTheme2To3 = (data) => { const leftoverKey = key.replace(prefix, '') const parts = (leftoverKey || 'Bg').match(/[A-Z][a-z]*/g) const last = parts.slice(-1)[0] - let newRule = { directives: {} } + let newRule = { source: '2to3', directives: {} } let variantArray = [] switch (last) { @@ -462,12 +465,12 @@ export const convertTheme2To3 = (data) => { if (prefix === 'popover' && variantArray[0] === 'Post') { newRule.component = 'Post' - newRule.parent = { component: 'Popover' } + newRule.parent = { source: '2to3hack', component: 'Popover' } variantArray = variantArray.filter(x => x !== 'Post') } if (prefix === 'selectedMenu' && variantArray[0] === 'Popover') { - newRule.parent = { component: 'Popover' } + newRule.parent = { source: '2to3hack', component: 'Popover' } variantArray = variantArray.filter(x => x !== 'Popover') } @@ -477,12 +480,12 @@ export const convertTheme2To3 = (data) => { case 'alert': { const hasPanel = variantArray.find(x => x === 'Panel') if (hasPanel) { - newRule.parent = { component: 'PanelHeader' } + newRule.parent = { source: '2to3hack', component: 'PanelHeader', parent: newRule.parent } variantArray = variantArray.filter(x => x !== 'Panel') } const hasTop = variantArray.find(x => x === 'Top') // TopBar if (hasTop) { - newRule.parent = { component: 'TopBar' } + newRule.parent = { source: '2to3hack', component: 'TopBar', parent: newRule.parent } variantArray = variantArray.filter(x => x !== 'Top' && x !== 'Bar') } break diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js index 6e477674..2dddfa04 100644 --- a/src/services/theme_data/theme_data.service.js +++ b/src/services/theme_data/theme_data.service.js @@ -117,7 +117,6 @@ export const topoSort = ( // Put it into the output list output.push(node) } else if (grays.has(node)) { - console.debug('Cyclic depenency in topoSort, ignoring') output.push(node) } else if (blacks.has(node)) { // do nothing diff --git a/src/services/theme_data/theme_data_3.service.js b/src/services/theme_data/theme_data_3.service.js index 047ba8a2..cf58da11 100644 --- a/src/services/theme_data/theme_data_3.service.js +++ b/src/services/theme_data/theme_data_3.service.js @@ -149,16 +149,42 @@ const ruleToSelector = genericRuleToSelector(components) export const getEngineChecksum = () => engineChecksum -export const init = (extraRuleset, ultimateBackgroundColor) => { +/** + * Initializes and compiles the theme according to the ruleset + * + * @param {Object[]} inputRuleset - set of rules to compile theme against. Acts as an override to + * component default rulesets + * @param {string} ultimateBackgroundColor - Color that will be the "final" background for + * calculating contrast ratios and making text automatically accessible. Really used for cases when + * stuff is transparent. + * @param {boolean} debug - print out debug information in console, mostly just performance stuff + * @param {boolean} liteMode - use validInnerComponentsLite instead of validInnerComponents, meant to + * generatate theme previews and such that need to be compiled faster and don't require a lot of other + * components present in "normal" mode + * @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, + onlyNormalState = false, + rootComponentName = 'Root' +}) => { + if (!inputRuleset) throw new Error('Ruleset is null or undefined!') const staticVars = {} const stacked = {} const computed = {} const rulesetUnsorted = [ ...Object.values(components) - .map(c => (c.defaultRules || []).map(r => ({ component: c.name, ...r }))) + .map(c => (c.defaultRules || []).map(r => ({ component: c.name, ...r, source: 'Built-in' }))) .reduce((acc, arr) => [...acc, ...arr], []), - ...extraRuleset + ...inputRuleset ].map(rule => { normalizeCombination(rule) let currentParent = rule.parent @@ -395,11 +421,16 @@ export const init = (extraRuleset, ultimateBackgroundColor) => { const processInnerComponent = (component, parent) => { const combinations = [] const { - validInnerComponents = [], states: originalStates = {}, variants: originalVariants = {} } = component + const validInnerComponents = ( + liteMode + ? (component.validInnerComponentsLite || component.validInnerComponents) + : component.validInnerComponents + ) || [] + // Normalizing states and variants to always include "normal" const states = { normal: '', ...originalStates } const variants = { normal: '', ...originalVariants } @@ -411,22 +442,26 @@ export const init = (extraRuleset, ultimateBackgroundColor) => { // Optimization: we only really need combinations without "normal" because all states implicitly have it const permutationStateKeys = Object.keys(states).filter(s => s !== 'normal') - const stateCombinations = [ - ['normal'], - ...getAllPossibleCombinations(permutationStateKeys) - .map(combination => ['normal', ...combination]) - .filter(combo => { - // Optimization: filter out some hard-coded combinations that don't make sense - if (combo.indexOf('disabled') >= 0) { - return !( - combo.indexOf('hover') >= 0 || - combo.indexOf('focused') >= 0 || - combo.indexOf('pressed') >= 0 - ) - } - return true - }) - ] + const stateCombinations = onlyNormalState + ? [ + ['normal'] + ] + : [ + ['normal'], + ...getAllPossibleCombinations(permutationStateKeys) + .map(combination => ['normal', ...combination]) + .filter(combo => { + // Optimization: filter out some hard-coded combinations that don't make sense + if (combo.indexOf('disabled') >= 0) { + return !( + combo.indexOf('hover') >= 0 || + combo.indexOf('focused') >= 0 || + combo.indexOf('pressed') >= 0 + ) + } + return true + }) + ] const stateVariantCombination = Object.keys(variants).map(variant => { return stateCombinations.map(state => ({ variant, state })) @@ -451,9 +486,11 @@ export const init = (extraRuleset, ultimateBackgroundColor) => { } const t0 = performance.now() - const combinations = processInnerComponent(components.Root) + const combinations = processInnerComponent(components[rootComponentName] ?? components.Root) const t1 = performance.now() - console.debug('Tree traveral took ' + (t1 - t0) + ' ms') + if (debug) { + console.debug('Tree traveral took ' + (t1 - t0) + ' ms') + } const result = combinations.map((combination) => { if (combination.lazy) { @@ -463,7 +500,9 @@ export const init = (extraRuleset, ultimateBackgroundColor) => { } }).filter(x => x) const t2 = performance.now() - console.debug('Eager processing took ' + (t2 - t1) + ' ms') + if (debug) { + console.debug('Eager processing took ' + (t2 - t1) + ' ms') + } return { lazy: result.filter(x => typeof x === 'function'), |
