pleroma-fe/src/services/theme_data/theme3_slot_functions.js

215 lines
6.7 KiB
JavaScript
Raw Normal View History

import { convert, brightness } from 'chromatism'
2026-01-06 16:22:52 +02:00
import {
alphaBlend,
arithmeticBlend,
getTextColor,
relativeLuminance,
} from '../color_convert/color_convert.js'
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 func = functions[funcName]
if (args.length < func.argsNeeded) {
2026-01-06 16:22:52 +02:00
throw new Error(
`$${funcName} requires at least ${func.argsNeeded} arguments, but ${args.length} were provided`,
)
}
2024-02-21 22:18:56 +02:00
return func.exec(args, { findColor, findShadow }, { dynamicVars, staticVars })
}
export const colorFunctions = {
alpha: {
argsNeeded: 2,
2026-01-06 16:22:52 +02:00
documentation:
'Changes alpha value of the color only to be used for CSS variables',
args: ['color: source color used', 'amount: alpha value'],
2024-02-21 22:18:56 +02:00
exec: (args, { findColor }, { dynamicVars, staticVars }) => {
const [color, amountArg] = args
2026-01-06 16:22:52 +02:00
const colorArg = convert(
findColor(color, { dynamicVars, staticVars }),
).rgb
const amount = Number(amountArg)
return { ...colorArg, a: amount }
2026-01-06 16:22:52 +02:00
},
},
2024-10-30 16:01:06 +02:00
brightness: {
argsNeeded: 2,
2024-11-07 18:34:59 +02:00
document: 'Changes brightness/lightness of color in HSL colorspace',
2026-01-06 16:22:52 +02:00
args: ['color: source color used', 'amount: lightness value'],
2024-10-30 16:01:06 +02:00
exec: (args, { findColor }, { dynamicVars, staticVars }) => {
const [color, amountArg] = args
2026-01-06 16:22:52 +02:00
const colorArg = convert(
findColor(color, { dynamicVars, staticVars }),
).hsl
2024-10-30 16:01:06 +02:00
colorArg.l += Number(amountArg)
return { ...convert(colorArg).rgb }
2026-01-06 16:22:52 +02:00
},
2024-10-30 16:01:06 +02:00
},
2024-03-04 19:53:45 +02:00
textColor: {
argsNeeded: 2,
2026-01-06 16:22:52 +02:00
documentation:
'Get text color with adequate contrast for given background and intended text color. Same function is used internally',
2024-11-07 18:34:59 +02:00
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
2026-01-06 16:22:52 +02:00
'no-auto' - don't do anything (useless as a color function)`,
2024-11-07 18:34:59 +02:00
],
2024-03-04 19:53:45 +02:00
exec: (args, { findColor }, { dynamicVars, staticVars }) => {
const [backgroundArg, foregroundArg, preserve = 'preserve'] = args
2026-01-06 16:22:52 +02:00
const background = convert(
findColor(backgroundArg, { dynamicVars, staticVars }),
).rgb
const foreground = convert(
findColor(foregroundArg, { dynamicVars, staticVars }),
).rgb
2024-03-04 19:53:45 +02:00
return getTextColor(background, foreground, preserve === 'preserve')
2026-01-06 16:22:52 +02:00
},
2024-03-04 19:53:45 +02:00
},
blend: {
argsNeeded: 3,
2024-11-07 18:34:59 +02:00
documentation: 'Alpha blending between two colors',
args: [
'background: bottom layer color',
'amount: opacity of top layer',
2026-01-06 16:22:52 +02:00
'foreground: upper layer color',
2024-11-07 18:34:59 +02:00
],
2024-02-21 22:18:56 +02:00
exec: (args, { findColor }, { dynamicVars, staticVars }) => {
const [backgroundArg, amountArg, foregroundArg] = args
2026-01-06 16:22:52 +02:00
const background = convert(
findColor(backgroundArg, { dynamicVars, staticVars }),
).rgb
const foreground = convert(
findColor(foregroundArg, { dynamicVars, staticVars }),
).rgb
const amount = Number(amountArg)
return alphaBlend(background, amount, foreground)
2026-01-06 16:22:52 +02:00
},
},
2025-06-24 12:35:50 +00:00
shift: {
argsNeeded: 2,
documentation: 'Arithmetic blend between two colors',
args: [
'origin: base color',
'value: shift value',
2026-01-06 16:22:52 +02:00
'operator: math operator to use (+ or -)',
2025-06-24 12:35:50 +00:00
],
exec: (args, { findColor }, { dynamicVars, staticVars }) => {
const [originArg, valueArg, operatorArg] = args
2026-01-06 16:22:52 +02:00
const origin = convert(
findColor(originArg, { dynamicVars, staticVars }),
).rgb
const value = convert(
findColor(valueArg, { dynamicVars, staticVars }),
).rgb
2025-06-24 12:35:50 +00:00
return arithmeticBlend(origin, value, operatorArg)
2026-01-06 16:22:52 +02:00
},
2025-06-24 12:35:50 +00:00
},
boost: {
argsNeeded: 2,
2026-01-06 16:22:52 +02:00
documentation:
'If given color is dark makes it darker, if color is light - makes it lighter',
args: ['color: source color', 'amount: how much darken/brighten the color'],
exec: (args, { findColor }, { dynamicVars, staticVars }) => {
const [colorArg, amountArg] = args
2026-01-06 16:22:52 +02:00
const color = convert(
findColor(colorArg, { dynamicVars, staticVars }),
).rgb
const amount = Number(amountArg)
const isLight = relativeLuminance(color) < 0.5
const mod = isLight ? -1 : 1
return brightness(amount * mod, color).rgb
2026-01-06 16:22:52 +02:00
},
},
mod: {
argsNeeded: 2,
2026-01-06 16:22:52 +02:00
documentation:
'Old function that increases or decreases brightness depending if background 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'],
2024-02-21 22:18:56 +02:00
exec: (args, { findColor }, { dynamicVars, staticVars }) => {
const [colorArg, amountArg] = args
2026-01-06 16:22:52 +02:00
const color = convert(
findColor(colorArg, { dynamicVars, staticVars }),
).rgb
const amount = Number(amountArg)
const effectiveBackground = dynamicVars.lowerLevelBackground ?? color
2026-01-06 16:22:52 +02:00
const isLightOnDark =
relativeLuminance(convert(effectiveBackground).rgb) < 0.5
const mod = isLightOnDark ? 1 : -1
return brightness(amount * mod, color).rgb
2026-01-06 16:22:52 +02:00
},
},
}
export const shadowFunctions = {
borderSide: {
argsNeeded: 3,
2026-01-06 16:22:52 +02:00
documentation:
'Simulate a border on a side with a shadow, best works on inset border',
2024-11-07 18:34:59 +02:00
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")',
'width: border width (thickness)',
2024-11-07 18:34:59 +02:00
'[alpha]: (Optional) border opacity, defaults to 1 (fully opaque)',
2026-01-06 16:22:52 +02:00
'[inset]: (Optional) whether border should be on the inside or outside, defaults to inside',
2024-11-07 18:34:59 +02:00
],
2025-02-04 15:23:21 +02:00
exec: (args) => {
const [color, side, alpha = '1', widthArg = '1', inset = 'inset'] = args
const width = Number(widthArg)
const isInset = inset === 'inset'
const targetShadow = {
x: 0,
y: 0,
blur: 0,
spread: 0,
color,
alpha: Number(alpha),
2026-01-06 16:22:52 +02:00
inset: isInset,
}
side.split('-').forEach((position) => {
switch (position) {
case 'left':
targetShadow.x = width * (inset ? 1 : -1)
break
case 'right':
targetShadow.x = -1 * width * (inset ? 1 : -1)
break
case 'top':
targetShadow.y = width * (inset ? 1 : -1)
break
case 'bottom':
targetShadow.y = -1 * width * (inset ? 1 : -1)
break
}
})
2024-02-21 22:18:56 +02:00
return [targetShadow]
2026-01-06 16:22:52 +02:00
},
},
}