logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://hacktivis.me/git/pleroma-fe.git
commit: da0db933d91c7658a33831ce167095073223285d
parent c3e6e7c61de8fcf9cdeeeac6479d8a00a9b47b3a
Author: Henry Jameson <me@hjkos.com>
Date:   Wed, 21 Feb 2024 12:23:10 +0200

moved themes2 stuff into theme_data.service.js

Diffstat:

Msrc/services/style_setter/style_setter.js348+------------------------------------------------------------------------------
Msrc/services/theme_data/theme_data.service.js346++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 347 insertions(+), 347 deletions(-)

diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js @@ -1,6 +1,5 @@ -import { convert } from 'chromatism' -import { rgb2hex, hex2rgb, rgba2css, getCssColor, relativeLuminance } from '../color_convert/color_convert.js' -import { getColors, computeDynamicColor, getOpacitySlot } from '../theme_data/theme_data.service.js' +import { hex2rgb } from '../color_convert/color_convert.js' +import { generatePreset } from '../theme_data/theme_data.service.js' import { init } 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' @@ -83,308 +82,6 @@ export const applyConfig = (config) => { body.classList.remove('hidden') } -export const getCssShadow = (input, usesDropShadow) => { - if (input.length === 0) { - return 'none' - } - - return input - .filter(_ => usesDropShadow ? _.inset : _) - .map((shad) => [ - shad.x, - shad.y, - shad.blur, - shad.spread - ].map(_ => _ + 'px').concat([ - getCssColor(shad.color, shad.alpha), - shad.inset ? 'inset' : '' - ]).join(' ')).join(', ') -} - -const getCssShadowFilter = (input) => { - if (input.length === 0) { - return 'none' - } - - return input - // drop-shadow doesn't support inset or spread - .filter((shad) => !shad.inset && Number(shad.spread) === 0) - .map((shad) => [ - shad.x, - shad.y, - // drop-shadow's blur is twice as strong compared to box-shadow - shad.blur / 2 - ].map(_ => _ + 'px').concat([ - getCssColor(shad.color, shad.alpha) - ]).join(' ')) - .map(_ => `drop-shadow(${_})`) - .join(' ') -} - -export const generateColors = (themeData) => { - const sourceColors = !themeData.themeEngineVersion - ? colors2to3(themeData.colors || themeData) - : themeData.colors || themeData - - const { colors, opacity } = getColors(sourceColors, themeData.opacity || {}) - - const htmlColors = Object.entries(colors) - .reduce((acc, [k, v]) => { - if (!v) return acc - acc.solid[k] = rgb2hex(v) - acc.complete[k] = typeof v.a === 'undefined' ? rgb2hex(v) : rgba2css(v) - return acc - }, { complete: {}, solid: {} }) - return { - rules: { - colors: Object.entries(htmlColors.complete) - .filter(([k, v]) => v) - .map(([k, v]) => `--${k}: ${v}`) - .join(';') - }, - theme: { - colors: htmlColors.solid, - opacity - } - } -} - -export const generateRadii = (input) => { - let inputRadii = input.radii || {} - // v1 -> v2 - if (typeof input.btnRadius !== 'undefined') { - inputRadii = Object - .entries(input) - .filter(([k, v]) => k.endsWith('Radius')) - .reduce((acc, e) => { acc[e[0].split('Radius')[0]] = e[1]; return acc }, {}) - } - const radii = Object.entries(inputRadii).filter(([k, v]) => v).reduce((acc, [k, v]) => { - acc[k] = v - return acc - }, { - btn: 4, - input: 4, - checkbox: 2, - panel: 10, - avatar: 5, - avatarAlt: 50, - tooltip: 2, - attachment: 5, - chatMessage: inputRadii.panel - }) - - return { - rules: { - radii: Object.entries(radii).filter(([k, v]) => v).map(([k, v]) => `--${k}Radius: ${v}px`).join(';') - }, - theme: { - radii - } - } -} - -export const generateFonts = (input) => { - const fonts = Object.entries(input.fonts || {}).filter(([k, v]) => v).reduce((acc, [k, v]) => { - acc[k] = Object.entries(v).filter(([k, v]) => v).reduce((acc, [k, v]) => { - acc[k] = v - return acc - }, acc[k]) - return acc - }, { - interface: { - family: 'sans-serif' - }, - input: { - family: 'inherit' - }, - post: { - family: 'inherit' - }, - postCode: { - family: 'monospace' - } - }) - - return { - rules: { - fonts: Object - .entries(fonts) - .filter(([k, v]) => v) - .map(([k, v]) => `--${k}Font: ${v.family}`).join(';') - }, - theme: { - fonts - } - } -} - -const border = (top, shadow) => ({ - x: 0, - y: top ? 1 : -1, - blur: 0, - spread: 0, - color: shadow ? '#000000' : '#FFFFFF', - alpha: 0.2, - inset: true -}) -const buttonInsetFakeBorders = [border(true, false), border(false, true)] -const inputInsetFakeBorders = [border(true, true), border(false, false)] -const hoverGlow = { - x: 0, - y: 0, - blur: 4, - spread: 0, - color: '--faint', - alpha: 1 -} - -export const DEFAULT_SHADOWS = { - panel: [{ - x: 1, - y: 1, - blur: 4, - spread: 0, - color: '#000000', - alpha: 0.6 - }], - topBar: [{ - x: 0, - y: 0, - blur: 4, - spread: 0, - color: '#000000', - alpha: 0.6 - }], - popup: [{ - x: 2, - y: 2, - blur: 3, - spread: 0, - color: '#000000', - alpha: 0.5 - }], - avatar: [{ - x: 0, - y: 1, - blur: 8, - spread: 0, - color: '#000000', - alpha: 0.7 - }], - avatarStatus: [], - panelHeader: [], - button: [{ - x: 0, - y: 0, - blur: 2, - spread: 0, - color: '#000000', - alpha: 1 - }, ...buttonInsetFakeBorders], - buttonHover: [hoverGlow, ...buttonInsetFakeBorders], - buttonPressed: [hoverGlow, ...inputInsetFakeBorders], - input: [...inputInsetFakeBorders, { - x: 0, - y: 0, - blur: 2, - inset: true, - spread: 0, - color: '#000000', - alpha: 1 - }] -} -export const generateShadows = (input, colors) => { - // TODO this is a small hack for `mod` to work with shadows - // this is used to get the "context" of shadow, i.e. for `mod` properly depend on background color of element - const hackContextDict = { - button: 'btn', - panel: 'bg', - top: 'topBar', - popup: 'popover', - avatar: 'bg', - panelHeader: 'panel', - input: 'input' - } - - const cleanInputShadows = Object.fromEntries( - Object.entries(input.shadows || {}) - .map(([name, shadowSlot]) => [ - name, - // defaulting color to black to avoid potential problems - shadowSlot.map(shadowDef => ({ color: '#000000', ...shadowDef })) - ]) - ) - const inputShadows = cleanInputShadows && !input.themeEngineVersion - ? shadows2to3(cleanInputShadows, input.opacity) - : cleanInputShadows || {} - const shadows = Object.entries({ - ...DEFAULT_SHADOWS, - ...inputShadows - }).reduce((shadowsAcc, [slotName, shadowDefs]) => { - const slotFirstWord = slotName.replace(/[A-Z].*$/, '') - const colorSlotName = hackContextDict[slotFirstWord] - const isLightOnDark = relativeLuminance(convert(colors[colorSlotName]).rgb) < 0.5 - const mod = isLightOnDark ? 1 : -1 - const newShadow = shadowDefs.reduce((shadowAcc, def) => [ - ...shadowAcc, - { - ...def, - color: rgb2hex(computeDynamicColor( - def.color, - (variableSlot) => convert(colors[variableSlot]).rgb, - mod - )) - } - ], []) - return { ...shadowsAcc, [slotName]: newShadow } - }, {}) - - return { - rules: { - shadows: Object - .entries(shadows) - // TODO for v2.2: if shadow doesn't have non-inset shadows with spread > 0 - optionally - // convert all non-inset shadows into filter: drop-shadow() to boost performance - .map(([k, v]) => [ - `--${k}Shadow: ${getCssShadow(v)}`, - `--${k}ShadowFilter: ${getCssShadowFilter(v)}`, - `--${k}ShadowInset: ${getCssShadow(v, true)}` - ].join(';')) - .join(';') - }, - theme: { - shadows - } - } -} - -export const composePreset = (colors, radii, shadows, fonts) => { - return { - rules: { - ...shadows.rules, - ...colors.rules, - ...radii.rules, - ...fonts.rules - }, - theme: { - ...shadows.theme, - ...colors.theme, - ...radii.theme, - ...fonts.theme - } - } -} - -export const generatePreset = (input) => { - const colors = generateColors(input) - return composePreset( - colors, - generateRadii(input), - generateShadows(input, colors.theme.colors, colors.mod), - generateFonts(input) - ) -} - export const getThemes = () => { const cache = 'no-store' @@ -414,47 +111,6 @@ export const getThemes = () => { }, {}) }) } -export const colors2to3 = (colors) => { - return Object.entries(colors).reduce((acc, [slotName, color]) => { - const btnPositions = ['', 'Panel', 'TopBar'] - switch (slotName) { - case 'lightBg': - return { ...acc, highlight: color } - case 'btnText': - return { - ...acc, - ...btnPositions - .reduce( - (statePositionAcc, position) => - ({ ...statePositionAcc, ['btn' + position + 'Text']: color }) - , {} - ) - } - default: - return { ...acc, [slotName]: color } - } - }, {}) -} - -/** - * This handles compatibility issues when importing v2 theme's shadows to current format - * - * Back in v2 shadows allowed you to use dynamic colors however those used pure CSS3 variables - */ -export const shadows2to3 = (shadows, opacity) => { - return Object.entries(shadows).reduce((shadowsAcc, [slotName, shadowDefs]) => { - const isDynamic = ({ color = '#000000' }) => color.startsWith('--') - const getOpacity = ({ color }) => opacity[getOpacitySlot(color.substring(2).split(',')[0])] - const newShadow = shadowDefs.reduce((shadowAcc, def) => [ - ...shadowAcc, - { - ...def, - alpha: isDynamic(def) ? getOpacity(def) || 1 : def.alpha - } - ], []) - return { ...shadowsAcc, [slotName]: newShadow } - }, {}) -} export const getPreset = (val) => { return getThemes() diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js @@ -1,5 +1,5 @@ import { convert, brightness, contrastRatio } from 'chromatism' -import { alphaBlendLayers, getTextColor, relativeLuminance } from '../color_convert/color_convert.js' +import { rgb2hex, rgba2css, alphaBlendLayers, getTextColor, relativeLuminance, getCssColor } from '../color_convert/color_convert.js' import { LAYERS, DEFAULT_OPACITY, SLOT_INHERITANCE } from './pleromafe.js' /* @@ -407,3 +407,347 @@ export const getColors = (sourceColors, sourceOpacity) => SLOT_ORDERED.reduce(({ } } }, { colors: {}, opacity: {} }) + +export const composePreset = (colors, radii, shadows, fonts) => { + return { + rules: { + ...shadows.rules, + ...colors.rules, + ...radii.rules, + ...fonts.rules + }, + theme: { + ...shadows.theme, + ...colors.theme, + ...radii.theme, + ...fonts.theme + } + } +} + +export const generatePreset = (input) => { + const colors = generateColors(input) + return composePreset( + colors, + generateRadii(input), + generateShadows(input, colors.theme.colors, colors.mod), + generateFonts(input) + ) +} + +export const getCssShadow = (input, usesDropShadow) => { + if (input.length === 0) { + return 'none' + } + + return input + .filter(_ => usesDropShadow ? _.inset : _) + .map((shad) => [ + shad.x, + shad.y, + shad.blur, + shad.spread + ].map(_ => _ + 'px').concat([ + getCssColor(shad.color, shad.alpha), + shad.inset ? 'inset' : '' + ]).join(' ')).join(', ') +} + +const getCssShadowFilter = (input) => { + if (input.length === 0) { + return 'none' + } + + return input + // drop-shadow doesn't support inset or spread + .filter((shad) => !shad.inset && Number(shad.spread) === 0) + .map((shad) => [ + shad.x, + shad.y, + // drop-shadow's blur is twice as strong compared to box-shadow + shad.blur / 2 + ].map(_ => _ + 'px').concat([ + getCssColor(shad.color, shad.alpha) + ]).join(' ')) + .map(_ => `drop-shadow(${_})`) + .join(' ') +} + +export const generateColors = (themeData) => { + const sourceColors = !themeData.themeEngineVersion + ? colors2to3(themeData.colors || themeData) + : themeData.colors || themeData + + const { colors, opacity } = getColors(sourceColors, themeData.opacity || {}) + + const htmlColors = Object.entries(colors) + .reduce((acc, [k, v]) => { + if (!v) return acc + acc.solid[k] = rgb2hex(v) + acc.complete[k] = typeof v.a === 'undefined' ? rgb2hex(v) : rgba2css(v) + return acc + }, { complete: {}, solid: {} }) + return { + rules: { + colors: Object.entries(htmlColors.complete) + .filter(([k, v]) => v) + .map(([k, v]) => `--${k}: ${v}`) + .join(';') + }, + theme: { + colors: htmlColors.solid, + opacity + } + } +} + +export const generateRadii = (input) => { + let inputRadii = input.radii || {} + // v1 -> v2 + if (typeof input.btnRadius !== 'undefined') { + inputRadii = Object + .entries(input) + .filter(([k, v]) => k.endsWith('Radius')) + .reduce((acc, e) => { acc[e[0].split('Radius')[0]] = e[1]; return acc }, {}) + } + const radii = Object.entries(inputRadii).filter(([k, v]) => v).reduce((acc, [k, v]) => { + acc[k] = v + return acc + }, { + btn: 4, + input: 4, + checkbox: 2, + panel: 10, + avatar: 5, + avatarAlt: 50, + tooltip: 2, + attachment: 5, + chatMessage: inputRadii.panel + }) + + return { + rules: { + radii: Object.entries(radii).filter(([k, v]) => v).map(([k, v]) => `--${k}Radius: ${v}px`).join(';') + }, + theme: { + radii + } + } +} + +export const generateFonts = (input) => { + const fonts = Object.entries(input.fonts || {}).filter(([k, v]) => v).reduce((acc, [k, v]) => { + acc[k] = Object.entries(v).filter(([k, v]) => v).reduce((acc, [k, v]) => { + acc[k] = v + return acc + }, acc[k]) + return acc + }, { + interface: { + family: 'sans-serif' + }, + input: { + family: 'inherit' + }, + post: { + family: 'inherit' + }, + postCode: { + family: 'monospace' + } + }) + + return { + rules: { + fonts: Object + .entries(fonts) + .filter(([k, v]) => v) + .map(([k, v]) => `--${k}Font: ${v.family}`).join(';') + }, + theme: { + fonts + } + } +} + +const border = (top, shadow) => ({ + x: 0, + y: top ? 1 : -1, + blur: 0, + spread: 0, + color: shadow ? '#000000' : '#FFFFFF', + alpha: 0.2, + inset: true +}) +const buttonInsetFakeBorders = [border(true, false), border(false, true)] +const inputInsetFakeBorders = [border(true, true), border(false, false)] +const hoverGlow = { + x: 0, + y: 0, + blur: 4, + spread: 0, + color: '--faint', + alpha: 1 +} + +export const DEFAULT_SHADOWS = { + panel: [{ + x: 1, + y: 1, + blur: 4, + spread: 0, + color: '#000000', + alpha: 0.6 + }], + topBar: [{ + x: 0, + y: 0, + blur: 4, + spread: 0, + color: '#000000', + alpha: 0.6 + }], + popup: [{ + x: 2, + y: 2, + blur: 3, + spread: 0, + color: '#000000', + alpha: 0.5 + }], + avatar: [{ + x: 0, + y: 1, + blur: 8, + spread: 0, + color: '#000000', + alpha: 0.7 + }], + avatarStatus: [], + panelHeader: [], + button: [{ + x: 0, + y: 0, + blur: 2, + spread: 0, + color: '#000000', + alpha: 1 + }, ...buttonInsetFakeBorders], + buttonHover: [hoverGlow, ...buttonInsetFakeBorders], + buttonPressed: [hoverGlow, ...inputInsetFakeBorders], + input: [...inputInsetFakeBorders, { + x: 0, + y: 0, + blur: 2, + inset: true, + spread: 0, + color: '#000000', + alpha: 1 + }] +} +export const generateShadows = (input, colors) => { + // TODO this is a small hack for `mod` to work with shadows + // this is used to get the "context" of shadow, i.e. for `mod` properly depend on background color of element + const hackContextDict = { + button: 'btn', + panel: 'bg', + top: 'topBar', + popup: 'popover', + avatar: 'bg', + panelHeader: 'panel', + input: 'input' + } + + const cleanInputShadows = Object.fromEntries( + Object.entries(input.shadows || {}) + .map(([name, shadowSlot]) => [ + name, + // defaulting color to black to avoid potential problems + shadowSlot.map(shadowDef => ({ color: '#000000', ...shadowDef })) + ]) + ) + const inputShadows = cleanInputShadows && !input.themeEngineVersion + ? shadows2to3(cleanInputShadows, input.opacity) + : cleanInputShadows || {} + const shadows = Object.entries({ + ...DEFAULT_SHADOWS, + ...inputShadows + }).reduce((shadowsAcc, [slotName, shadowDefs]) => { + const slotFirstWord = slotName.replace(/[A-Z].*$/, '') + const colorSlotName = hackContextDict[slotFirstWord] + const isLightOnDark = relativeLuminance(convert(colors[colorSlotName]).rgb) < 0.5 + const mod = isLightOnDark ? 1 : -1 + const newShadow = shadowDefs.reduce((shadowAcc, def) => [ + ...shadowAcc, + { + ...def, + color: rgb2hex(computeDynamicColor( + def.color, + (variableSlot) => convert(colors[variableSlot]).rgb, + mod + )) + } + ], []) + return { ...shadowsAcc, [slotName]: newShadow } + }, {}) + + return { + rules: { + shadows: Object + .entries(shadows) + // TODO for v2.2: if shadow doesn't have non-inset shadows with spread > 0 - optionally + // convert all non-inset shadows into filter: drop-shadow() to boost performance + .map(([k, v]) => [ + `--${k}Shadow: ${getCssShadow(v)}`, + `--${k}ShadowFilter: ${getCssShadowFilter(v)}`, + `--${k}ShadowInset: ${getCssShadow(v, true)}` + ].join(';')) + .join(';') + }, + theme: { + shadows + } + } +} + +/** + * This handles compatibility issues when importing v2 theme's shadows to current format + * + * Back in v2 shadows allowed you to use dynamic colors however those used pure CSS3 variables + */ +export const shadows2to3 = (shadows, opacity) => { + return Object.entries(shadows).reduce((shadowsAcc, [slotName, shadowDefs]) => { + const isDynamic = ({ color = '#000000' }) => color.startsWith('--') + const getOpacity = ({ color }) => opacity[getOpacitySlot(color.substring(2).split(',')[0])] + const newShadow = shadowDefs.reduce((shadowAcc, def) => [ + ...shadowAcc, + { + ...def, + alpha: isDynamic(def) ? getOpacity(def) || 1 : def.alpha + } + ], []) + return { ...shadowsAcc, [slotName]: newShadow } + }, {}) +} + +export const colors2to3 = (colors) => { + return Object.entries(colors).reduce((acc, [slotName, color]) => { + const btnPositions = ['', 'Panel', 'TopBar'] + switch (slotName) { + case 'lightBg': + return { ...acc, highlight: color } + case 'btnText': + return { + ...acc, + ...btnPositions + .reduce( + (statePositionAcc, position) => + ({ ...statePositionAcc, ['btn' + position + 'Text']: color }) + , {} + ) + } + default: + return { ...acc, [slotName]: color } + } + }, {}) +}