logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://anongit.hacktivis.me/git/pleroma-fe.git/

style_setter.js (8315B)


  1. import { init, getEngineChecksum } from '../theme_data/theme_data_3.service.js'
  2. import { getCssRules } from '../theme_data/css_utils.js'
  3. import { defaultState } from 'src/modules/default_config_state.js'
  4. import { chunk } from 'lodash'
  5. import pako from 'pako'
  6. import localforage from 'localforage'
  7. console.log('CONFIG', defaultState)
  8. // On platforms where this is not supported, it will return undefined
  9. // Otherwise it will return an array
  10. const supportsAdoptedStyleSheets = !!document.adoptedStyleSheets
  11. const createStyleSheet = (id) => {
  12. if (supportsAdoptedStyleSheets) {
  13. return {
  14. el: null,
  15. sheet: new CSSStyleSheet(),
  16. rules: []
  17. }
  18. }
  19. const el = document.getElementById(id)
  20. // Clear all rules in it
  21. for (let i = el.sheet.cssRules.length - 1; i >= 0; --i) {
  22. el.sheet.deleteRule(i)
  23. }
  24. return {
  25. el,
  26. sheet: el.sheet,
  27. rules: []
  28. }
  29. }
  30. const EAGER_STYLE_ID = 'pleroma-eager-styles'
  31. const LAZY_STYLE_ID = 'pleroma-lazy-styles'
  32. const adoptStyleSheets = (styles) => {
  33. if (supportsAdoptedStyleSheets) {
  34. document.adoptedStyleSheets = styles.map(s => s.sheet)
  35. }
  36. // Some older browsers do not support document.adoptedStyleSheets.
  37. // In this case, we use the <style> elements.
  38. // Since the <style> elements we need are already in the DOM, there
  39. // is nothing to do here.
  40. }
  41. export const generateTheme = (inputRuleset, callbacks, debug) => {
  42. const {
  43. onNewRule = () => {},
  44. onLazyFinished = () => {},
  45. onEagerFinished = () => {}
  46. } = callbacks
  47. const themes3 = init({
  48. inputRuleset,
  49. debug
  50. })
  51. getCssRules(themes3.eager, debug).forEach(rule => {
  52. // Hacks to support multiple selectors on same component
  53. onNewRule(rule, false)
  54. })
  55. onEagerFinished()
  56. // Optimization - instead of processing all lazy rules in one go, process them in small chunks
  57. // so that UI can do other things and be somewhat responsive while less important rules are being
  58. // processed
  59. let counter = 0
  60. const chunks = chunk(themes3.lazy, 200)
  61. // let t0 = performance.now()
  62. const processChunk = () => {
  63. const chunk = chunks[counter]
  64. Promise.all(chunk.map(x => x())).then(result => {
  65. getCssRules(result.filter(x => x), debug).forEach(rule => {
  66. onNewRule(rule, true)
  67. })
  68. // const t1 = performance.now()
  69. // console.debug('Chunk ' + counter + ' took ' + (t1 - t0) + 'ms')
  70. // t0 = t1
  71. counter += 1
  72. if (counter < chunks.length) {
  73. setTimeout(processChunk, 0)
  74. } else {
  75. onLazyFinished()
  76. }
  77. })
  78. }
  79. return { lazyProcessFunc: processChunk }
  80. }
  81. export const tryLoadCache = async () => {
  82. console.info('Trying to load compiled theme data from cache')
  83. const data = await localforage.getItem('pleromafe-theme-cache')
  84. if (!data) return null
  85. let cache
  86. try {
  87. const inflated = pako.inflate(data)
  88. const decoded = new TextDecoder().decode(inflated)
  89. cache = JSON.parse(decoded)
  90. console.info(`Loaded theme from cache, compressed=${Math.ceil(data.length / 1024)}kiB size=${Math.ceil(inflated.length / 1024)}kiB`)
  91. } catch (e) {
  92. console.error('Failed to decode theme cache:', e)
  93. return false
  94. }
  95. if (cache.engineChecksum === getEngineChecksum()) {
  96. const eagerStyles = createStyleSheet(EAGER_STYLE_ID)
  97. const lazyStyles = createStyleSheet(LAZY_STYLE_ID)
  98. cache.data[0].forEach(rule => eagerStyles.sheet.insertRule(rule, 'index-max'))
  99. cache.data[1].forEach(rule => lazyStyles.sheet.insertRule(rule, 'index-max'))
  100. adoptStyleSheets([eagerStyles, lazyStyles])
  101. return true
  102. } else {
  103. console.warn('Engine checksum doesn\'t match, cache not usable, clearing')
  104. localStorage.removeItem('pleroma-fe-theme-cache')
  105. }
  106. }
  107. export const applyTheme = (
  108. input,
  109. onEagerFinish = () => {},
  110. onFinish = () => {},
  111. debug
  112. ) => {
  113. const eagerStyles = createStyleSheet(EAGER_STYLE_ID)
  114. const lazyStyles = createStyleSheet(LAZY_STYLE_ID)
  115. const insertRule = (styles, rule) => {
  116. if (rule.indexOf('webkit') >= 0) {
  117. try {
  118. styles.sheet.insertRule(rule, 'index-max')
  119. styles.rules.push(rule)
  120. } catch (e) {
  121. console.warn('Can\'t insert rule due to lack of support', e)
  122. }
  123. } else {
  124. styles.sheet.insertRule(rule, 'index-max')
  125. styles.rules.push(rule)
  126. }
  127. }
  128. const { lazyProcessFunc } = generateTheme(
  129. input,
  130. {
  131. onNewRule (rule, isLazy) {
  132. if (isLazy) {
  133. insertRule(lazyStyles, rule)
  134. } else {
  135. insertRule(eagerStyles, rule)
  136. }
  137. },
  138. onEagerFinished () {
  139. adoptStyleSheets([eagerStyles])
  140. onEagerFinish()
  141. },
  142. onLazyFinished () {
  143. adoptStyleSheets([eagerStyles, lazyStyles])
  144. const cache = { engineChecksum: getEngineChecksum(), data: [eagerStyles.rules, lazyStyles.rules] }
  145. onFinish(cache)
  146. const compress = (js) => {
  147. return pako.deflate(JSON.stringify(js))
  148. }
  149. localforage.setItem('pleromafe-theme-cache', compress(cache))
  150. }
  151. },
  152. debug
  153. )
  154. setTimeout(lazyProcessFunc, 0)
  155. }
  156. const extractStyleConfig = ({
  157. sidebarColumnWidth,
  158. contentColumnWidth,
  159. notifsColumnWidth,
  160. emojiReactionsScale,
  161. emojiSize,
  162. navbarSize,
  163. panelHeaderSize,
  164. textSize,
  165. forcedRoundness
  166. }) => {
  167. const result = {
  168. sidebarColumnWidth,
  169. contentColumnWidth,
  170. notifsColumnWidth,
  171. emojiReactionsScale,
  172. emojiSize,
  173. navbarSize,
  174. panelHeaderSize,
  175. textSize
  176. }
  177. switch (forcedRoundness) {
  178. case 'disable':
  179. break
  180. case '0':
  181. result.forcedRoundness = '0'
  182. break
  183. case '1':
  184. result.forcedRoundness = '1px'
  185. break
  186. case '2':
  187. result.forcedRoundness = '0.4rem'
  188. break
  189. default:
  190. }
  191. return result
  192. }
  193. console.log(defaultState)
  194. const defaultStyleConfig = extractStyleConfig(defaultState)
  195. export const applyConfig = (input) => {
  196. const config = extractStyleConfig(input)
  197. if (config === defaultStyleConfig) {
  198. return
  199. }
  200. const head = document.head
  201. const rules = Object
  202. .entries(config)
  203. .filter(([, v]) => v)
  204. .map(([k, v]) => `--${k}: ${v}`).join(';')
  205. document.getElementById('style-config')?.remove()
  206. const styleEl = document.createElement('style')
  207. styleEl.id = 'style-config'
  208. head.appendChild(styleEl)
  209. const styleSheet = styleEl.sheet
  210. styleSheet.toString()
  211. styleSheet.insertRule(`:root { ${rules} }`, 'index-max')
  212. // TODO find a way to make this not apply to theme previews
  213. if (Object.prototype.hasOwnProperty.call(config, 'forcedRoundness')) {
  214. styleSheet.insertRule(` *:not(.preview-block) {
  215. --roundness: var(--forcedRoundness) !important;
  216. }`, 'index-max')
  217. }
  218. }
  219. export const getResourcesIndex = async (url, parser = JSON.parse) => {
  220. const cache = 'no-store'
  221. const customUrl = url.replace(/\.(\w+)$/, '.custom.$1')
  222. let builtin
  223. let custom
  224. const resourceTransform = (resources) => {
  225. return Object
  226. .entries(resources)
  227. .map(([k, v]) => {
  228. if (typeof v === 'object') {
  229. return [k, () => Promise.resolve(v)]
  230. } else if (typeof v === 'string') {
  231. return [
  232. k,
  233. () => window
  234. .fetch(v, { cache })
  235. .then(data => data.text())
  236. .then(text => parser(text))
  237. .catch(e => {
  238. console.error(e)
  239. return null
  240. })
  241. ]
  242. } else {
  243. console.error(`Unknown resource format - ${k} is a ${typeof v}`)
  244. return [k, null]
  245. }
  246. })
  247. }
  248. try {
  249. const builtinData = await window.fetch(url, { cache })
  250. const builtinResources = await builtinData.json()
  251. builtin = resourceTransform(builtinResources)
  252. } catch {
  253. builtin = []
  254. console.warn(`Builtin resources at ${url} unavailable`)
  255. }
  256. try {
  257. const customData = await window.fetch(customUrl, { cache })
  258. const customResources = await customData.json()
  259. custom = resourceTransform(customResources)
  260. } catch {
  261. custom = []
  262. console.warn(`Custom resources at ${customUrl} unavailable`)
  263. }
  264. const total = [...custom, ...builtin]
  265. if (total.length === 0) {
  266. return Promise.reject(new Error(`Resource at ${url} and ${customUrl} completely unavailable. Panicking`))
  267. }
  268. return Promise.resolve(Object.fromEntries(total))
  269. }