pleroma-fe/src/services/style_setter/style_setter.js

317 lines
8.5 KiB
JavaScript
Raw Normal View History

import { hex2rgb } from '../color_convert/color_convert.js'
2024-04-24 15:09:52 +03:00
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'
2024-05-31 14:33:44 -04:00
// On platforms where this is not supported, it will return undefined
// Otherwise it will return an array
const supportsAdoptedStyleSheets = !!document.adoptedStyleSheets
const createStyleSheet = (id) => {
if (supportsAdoptedStyleSheets) {
return {
el: null,
sheet: new CSSStyleSheet(),
rules: []
}
}
const el = document.getElementById(id)
// Clear all rules in it
for (let i = el.sheet.cssRules.length - 1; i >= 0; --i) {
el.sheet.deleteRule(i)
}
return {
el,
sheet: el.sheet,
rules: []
}
}
const EAGER_STYLE_ID = 'pleroma-eager-styles'
const LAZY_STYLE_ID = 'pleroma-lazy-styles'
const adoptStyleSheets = (styles) => {
if (supportsAdoptedStyleSheets) {
document.adoptedStyleSheets = styles.map(s => s.sheet)
}
// Some older browsers do not support document.adoptedStyleSheets.
// In this case, we use the <style> elements.
// Since the <style> elements we need are already in the DOM, there
// is nothing to do here.
}
export const generateTheme = async (inputRuleset, callbacks, debug) => {
const {
onNewRule = (rule, isLazy) => {},
onLazyFinished = () => {},
onEagerFinished = () => {}
} = callbacks
// Assuming that "worst case scenario background" is panel background since it's the most likely one
2024-07-04 03:20:26 +03:00
const themes3 = init({
inputRuleset,
ultimateBackgroundColor: inputRuleset[0].directives['--bg'].split('|')[1].trim(),
2024-07-04 03:20:26 +03:00
debug
})
getCssRules(themes3.eager, debug).forEach(rule => {
2024-03-06 20:27:05 +02:00
// 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)
}
2024-01-31 17:39:51 +02:00
})
onEagerFinished()
// Optimization - instead of processing all lazy rules in one go, process them in small chunks
// so that UI can do other things and be somewhat responsive while less important rules are being
// processed
let counter = 0
const chunks = chunk(themes3.lazy, 200)
// let t0 = performance.now()
const processChunk = () => {
const chunk = chunks[counter]
Promise.all(chunk.map(x => x())).then(result => {
getCssRules(result.filter(x => x), debug).forEach(rule => {
2024-03-06 20:27:05 +02:00
if (rule.match(/\.modal-view/)) {
const parts = rule.split(/[{}]/g)
const newRule = [
parts[0],
', ',
parts[0].replace(/\.modal-view/, '#modal'),
2024-04-03 21:27:19 +03:00
', ',
parts[0].replace(/\.modal-view/, '.shout-panel'),
2024-03-06 20:27:05 +02:00
' {',
parts[1],
'}'
].join('')
onNewRule(newRule, true)
2024-03-06 20:27:05 +02:00
} else {
onNewRule(rule, true)
2024-03-06 20:27:05 +02:00
}
})
// const t1 = performance.now()
// console.debug('Chunk ' + counter + ' took ' + (t1 - t0) + 'ms')
// t0 = t1
counter += 1
if (counter < chunks.length) {
setTimeout(processChunk, 0)
} else {
onLazyFinished()
}
})
}
return { lazyProcessFunc: processChunk }
}
export const tryLoadCache = () => {
const json = localStorage.getItem('pleroma-fe-theme-cache')
if (!json) return null
let cache
try {
cache = JSON.parse(json)
} catch (e) {
console.error('Failed to decode theme cache:', e)
return false
}
2024-04-24 15:09:52 +03:00
if (cache.engineChecksum === getEngineChecksum()) {
2024-05-31 14:33:44 -04:00
const eagerStyles = createStyleSheet(EAGER_STYLE_ID)
const lazyStyles = createStyleSheet(LAZY_STYLE_ID)
2024-05-31 14:33:44 -04:00
cache.data[0].forEach(rule => eagerStyles.sheet.insertRule(rule, 'index-max'))
cache.data[1].forEach(rule => lazyStyles.sheet.insertRule(rule, 'index-max'))
2024-05-31 14:33:44 -04:00
adoptStyleSheets([eagerStyles, lazyStyles])
return true
} else {
2024-04-24 15:09:52 +03:00
console.warn('Engine checksum doesn\'t match, cache not usable, clearing')
localStorage.removeItem('pleroma-fe-theme-cache')
}
}
export const applyTheme = async (input, onFinish = (data) => {}, debug) => {
2024-05-31 14:33:44 -04:00
const eagerStyles = createStyleSheet(EAGER_STYLE_ID)
const lazyStyles = createStyleSheet(LAZY_STYLE_ID)
const { lazyProcessFunc } = await generateTheme(
input,
{
onNewRule (rule, isLazy) {
if (isLazy) {
2024-05-31 14:33:44 -04:00
lazyStyles.sheet.insertRule(rule, 'index-max')
lazyStyles.rules.push(rule)
} else {
2024-05-31 14:33:44 -04:00
eagerStyles.sheet.insertRule(rule, 'index-max')
eagerStyles.rules.push(rule)
}
},
onEagerFinished () {
2024-05-31 14:33:44 -04:00
adoptStyleSheets([eagerStyles])
},
onLazyFinished () {
2024-05-31 14:33:44 -04:00
adoptStyleSheets([eagerStyles, lazyStyles])
const cache = { engineChecksum: getEngineChecksum(), data: [eagerStyles.rules, lazyStyles.rules] }
onFinish(cache)
2024-09-16 02:34:02 +03:00
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)
}
}
}
},
debug
)
setTimeout(lazyProcessFunc, 0)
return Promise.resolve()
}
2024-06-13 02:22:47 +03:00
const extractStyleConfig = ({
sidebarColumnWidth,
contentColumnWidth,
notifsColumnWidth,
emojiReactionsScale,
2024-06-13 02:22:47 +03:00
emojiSize,
navbarSize,
panelHeaderSize,
2024-06-21 22:46:01 +03:00
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
}
2024-06-13 02:22:47 +03:00
const defaultStyleConfig = extractStyleConfig(defaultState)
2024-09-16 02:34:02 +03:00
export const applyConfig = (input, i18n) => {
2024-06-13 02:22:47 +03:00
const config = extractStyleConfig(input)
2024-06-13 02:22:47 +03:00
if (config === defaultStyleConfig) {
return
}
const head = document.head
const rules = Object
2024-06-13 02:22:47 +03:00
.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')
2024-06-21 22:46:01 +03:00
if (Object.prototype.hasOwnProperty.call(config, 'forcedRoundness')) {
styleSheet.insertRule(` * {
--roundness: var(--forcedRoundness) !important;
}`, 'index-max')
}
}
2020-01-12 04:00:41 +02:00
export const getThemes = () => {
2020-02-11 10:42:15 +02:00
const cache = 'no-store'
return window.fetch('/static/styles.json', { cache })
.then((data) => data.json())
.then((themes) => {
return Object.entries(themes).map(([k, v]) => {
let promise = null
2018-12-11 01:38:20 +03:00
if (typeof v === 'object') {
promise = Promise.resolve(v)
2018-12-11 01:38:20 +03:00
} else if (typeof v === 'string') {
2020-02-11 10:42:15 +02:00
promise = window.fetch(v, { cache })
2018-12-11 01:38:20 +03:00
.then((data) => data.json())
.catch((e) => {
console.error(e)
return null
2018-12-11 01:38:20 +03:00
})
}
return [k, promise]
})
2018-12-11 01:38:20 +03:00
})
.then((promises) => {
return promises
.reduce((acc, [k, v]) => {
acc[k] = v
return acc
}, {})
})
}
export const getPreset = (val) => {
return getThemes()
2020-01-20 00:34:49 +02:00
.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 }
}
2024-02-22 01:10:24 +02:00
return { theme: data, source: theme.source }
})
2017-01-16 17:44:26 +01:00
}