aboutsummaryrefslogtreecommitdiff
path: root/src/services
diff options
context:
space:
mode:
Diffstat (limited to 'src/services')
-rw-r--r--src/services/style_setter/style_setter.js89
-rw-r--r--src/services/theme_data/css_utils.js24
-rw-r--r--src/services/theme_data/iss_utils.js39
-rw-r--r--src/services/theme_data/theme2_to_theme3.js25
-rw-r--r--src/services/theme_data/theme_data.service.js1
-rw-r--r--src/services/theme_data/theme_data_3.service.js85
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'),