logo

pleroma-fe

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

theme2_to_theme3.js (14653B)


  1. import { convert } from 'chromatism'
  2. import allKeys from './theme2_keys'
  3. // keys that are meant to be used globally, i.e. what's the rest of the theme is based upon.
  4. export const basePaletteKeys = new Set([
  5. 'bg',
  6. 'fg',
  7. 'text',
  8. 'link',
  9. 'accent',
  10. 'cBlue',
  11. 'cRed',
  12. 'cGreen',
  13. 'cOrange'
  14. ])
  15. export const fontsKeys = new Set([
  16. 'interface',
  17. 'input',
  18. 'post',
  19. 'postCode'
  20. ])
  21. export const opacityKeys = new Set([
  22. 'alert',
  23. 'alertPopup',
  24. 'bg',
  25. 'border',
  26. 'btn',
  27. 'faint',
  28. 'input',
  29. 'panel',
  30. 'popover',
  31. 'profileTint',
  32. 'underlay'
  33. ])
  34. export const shadowsKeys = new Set([
  35. 'panel',
  36. 'topBar',
  37. 'popup',
  38. 'avatar',
  39. 'avatarStatus',
  40. 'panelHeader',
  41. 'button',
  42. 'buttonHover',
  43. 'buttonPressed',
  44. 'input'
  45. ])
  46. export const radiiKeys = new Set([
  47. 'btn',
  48. 'input',
  49. 'checkbox',
  50. 'panel',
  51. 'avatar',
  52. 'avatarAlt',
  53. 'tooltip',
  54. 'attachment',
  55. 'chatMessage'
  56. ])
  57. // Keys that are not available in editor and never meant to be edited
  58. export const hiddenKeys = new Set([
  59. 'profileBg',
  60. 'profileTint'
  61. ])
  62. export const extendedBasePrefixes = [
  63. 'border',
  64. 'icon',
  65. 'highlight',
  66. 'lightText',
  67. 'popover',
  68. 'panel',
  69. 'topBar',
  70. 'tab',
  71. 'btn',
  72. 'input',
  73. 'selectedMenu',
  74. 'alert',
  75. 'alertPopup',
  76. 'badge',
  77. 'post',
  78. 'selectedPost', // wrong nomenclature
  79. 'poll',
  80. 'chatBg',
  81. 'chatMessage'
  82. ]
  83. export const nonComponentPrefixes = new Set([
  84. 'border',
  85. 'icon',
  86. 'highlight',
  87. 'lightText',
  88. 'chatBg'
  89. ])
  90. export const extendedBaseKeys = Object.fromEntries(
  91. extendedBasePrefixes.map(prefix => [
  92. prefix,
  93. allKeys.filter(k => {
  94. if (prefix === 'alert') {
  95. return k.startsWith(prefix) && !k.startsWith('alertPopup')
  96. }
  97. return k.startsWith(prefix)
  98. })
  99. ])
  100. )
  101. // Keysets that are only really used intermideately, i.e. to generate other colors
  102. export const temporary = new Set([
  103. '',
  104. 'highlight'
  105. ])
  106. export const temporaryColors = {}
  107. export const convertTheme2To3 = (data) => {
  108. data.colors.accent = data.colors.accent || data.colors.link
  109. data.colors.link = data.colors.link || data.colors.accent
  110. const generateRoot = () => {
  111. const directives = {}
  112. basePaletteKeys.forEach(key => { directives['--' + key] = 'color | ' + convert(data.colors[key]).hex })
  113. return {
  114. component: 'Root',
  115. directives
  116. }
  117. }
  118. const convertOpacity = () => {
  119. const newRules = []
  120. Object.keys(data.opacity || {}).forEach(key => {
  121. if (!opacityKeys.has(key) || data.opacity[key] === undefined) return null
  122. const originalOpacity = data.opacity[key]
  123. const rule = {}
  124. switch (key) {
  125. case 'alert':
  126. rule.component = 'Alert'
  127. break
  128. case 'alertPopup':
  129. rule.component = 'Alert'
  130. rule.parent = { component: 'Popover' }
  131. break
  132. case 'bg':
  133. rule.component = 'Panel'
  134. break
  135. case 'border':
  136. rule.component = 'Border'
  137. break
  138. case 'btn':
  139. rule.component = 'Button'
  140. break
  141. case 'faint':
  142. rule.component = 'Text'
  143. rule.state = ['faint']
  144. break
  145. case 'input':
  146. rule.component = 'Input'
  147. break
  148. case 'panel':
  149. rule.component = 'PanelHeader'
  150. break
  151. case 'popover':
  152. rule.component = 'Popover'
  153. break
  154. case 'profileTint':
  155. return null
  156. case 'underlay':
  157. rule.component = 'Underlay'
  158. break
  159. }
  160. switch (key) {
  161. case 'alert':
  162. case 'alertPopup':
  163. case 'bg':
  164. case 'btn':
  165. case 'input':
  166. case 'panel':
  167. case 'popover':
  168. case 'underlay':
  169. rule.directives = { opacity: originalOpacity }
  170. break
  171. case 'faint':
  172. case 'border':
  173. rule.directives = { textOpacity: originalOpacity }
  174. break
  175. }
  176. newRules.push(rule)
  177. if (rule.component === 'Button') {
  178. newRules.push({ ...rule, component: 'ScrollbarElement' })
  179. newRules.push({ ...rule, component: 'Tab' })
  180. newRules.push({ ...rule, component: 'Tab', state: ['active'], directives: { opacity: 0 } })
  181. }
  182. if (rule.component === 'Panel') {
  183. newRules.push({ ...rule, component: 'Post' })
  184. }
  185. })
  186. return newRules
  187. }
  188. const convertRadii = () => {
  189. const newRules = []
  190. Object.keys(data.radii || {}).forEach(key => {
  191. if (!radiiKeys.has(key) || data.radii[key] === undefined) return null
  192. const originalRadius = data.radii[key]
  193. const rule = {}
  194. switch (key) {
  195. case 'btn':
  196. rule.component = 'Button'
  197. break
  198. case 'tab':
  199. rule.component = 'Tab'
  200. break
  201. case 'input':
  202. rule.component = 'Input'
  203. break
  204. case 'checkbox':
  205. rule.component = 'Input'
  206. rule.variant = 'checkbox'
  207. break
  208. case 'panel':
  209. rule.component = 'Panel'
  210. break
  211. case 'avatar':
  212. rule.component = 'Avatar'
  213. break
  214. case 'avatarAlt':
  215. rule.component = 'Avatar'
  216. rule.variant = 'compact'
  217. break
  218. case 'tooltip':
  219. rule.component = 'Popover'
  220. break
  221. case 'attachment':
  222. rule.component = 'Attachment'
  223. break
  224. case 'ChatMessage':
  225. rule.component = 'Button'
  226. break
  227. }
  228. rule.directives = {
  229. roundness: originalRadius
  230. }
  231. newRules.push(rule)
  232. if (rule.component === 'Button') {
  233. newRules.push({ ...rule, component: 'ScrollbarElement' })
  234. newRules.push({ ...rule, component: 'Tab' })
  235. }
  236. })
  237. return newRules
  238. }
  239. const convertFonts = () => {
  240. const newRules = []
  241. Object.keys(data.fonts || {}).forEach(key => {
  242. if (!fontsKeys.has(key)) return
  243. const originalFont = data.fonts[key].family
  244. const rule = {}
  245. switch (key) {
  246. case 'interface':
  247. case 'postCode':
  248. rule.component = 'Root'
  249. break
  250. case 'input':
  251. rule.component = 'Input'
  252. break
  253. case 'post':
  254. rule.component = 'RichContent'
  255. break
  256. }
  257. switch (key) {
  258. case 'interface':
  259. case 'input':
  260. case 'post':
  261. rule.directives = { '--font': 'generic | ' + originalFont }
  262. break
  263. case 'postCode':
  264. rule.directives = { '--monoFont': 'generic | ' + originalFont }
  265. newRules.push({ ...rule, component: 'RichContent' })
  266. break
  267. }
  268. newRules.push(rule)
  269. })
  270. return newRules
  271. }
  272. const convertShadows = () => {
  273. const newRules = []
  274. Object.keys(data.shadows || {}).forEach(key => {
  275. if (!shadowsKeys.has(key)) return
  276. const originalShadow = data.shadows[key]
  277. const rule = {}
  278. switch (key) {
  279. case 'panel':
  280. rule.component = 'Panel'
  281. break
  282. case 'topBar':
  283. rule.component = 'TopBar'
  284. break
  285. case 'popup':
  286. rule.component = 'Popover'
  287. break
  288. case 'avatar':
  289. rule.component = 'Avatar'
  290. break
  291. case 'avatarStatus':
  292. rule.component = 'Avatar'
  293. rule.parent = { component: 'Post' }
  294. break
  295. case 'panelHeader':
  296. rule.component = 'PanelHeader'
  297. break
  298. case 'button':
  299. rule.component = 'Button'
  300. break
  301. case 'buttonHover':
  302. rule.component = 'Button'
  303. rule.state = ['hover']
  304. break
  305. case 'buttonPressed':
  306. rule.component = 'Button'
  307. rule.state = ['pressed']
  308. break
  309. case 'input':
  310. rule.component = 'Input'
  311. break
  312. }
  313. rule.directives = {
  314. shadow: originalShadow
  315. }
  316. newRules.push(rule)
  317. if (key === 'topBar') {
  318. newRules.push({ ...rule, component: 'PanelHeader', parent: { component: 'MobileDrawer' } })
  319. }
  320. if (key === 'avatarStatus') {
  321. newRules.push({ ...rule, parent: { component: 'Notification' } })
  322. }
  323. if (key === 'buttonPressed') {
  324. newRules.push({ ...rule, state: ['toggled'] })
  325. newRules.push({ ...rule, state: ['toggled', 'focus'] })
  326. newRules.push({ ...rule, state: ['pressed', 'focus'] })
  327. }
  328. if (key === 'buttonHover') {
  329. newRules.push({ ...rule, state: ['toggled', 'hover'] })
  330. newRules.push({ ...rule, state: ['pressed', 'hover'] })
  331. newRules.push({ ...rule, state: ['toggled', 'focus', 'hover'] })
  332. newRules.push({ ...rule, state: ['pressed', 'focus', 'hover'] })
  333. }
  334. if (rule.component === 'Button') {
  335. newRules.push({ ...rule, component: 'ScrollbarElement' })
  336. newRules.push({ ...rule, component: 'Tab' })
  337. }
  338. })
  339. return newRules
  340. }
  341. const extendedRules = Object.entries(extendedBaseKeys).map(([prefix, keys]) => {
  342. if (nonComponentPrefixes.has(prefix)) return null
  343. const rule = {}
  344. if (prefix === 'alertPopup') {
  345. rule.component = 'Alert'
  346. rule.parent = { component: 'Popover' }
  347. } else if (prefix === 'selectedPost') {
  348. rule.component = 'Post'
  349. rule.state = ['selected']
  350. } else if (prefix === 'selectedMenu') {
  351. rule.component = 'MenuItem'
  352. rule.state = ['hover']
  353. } else if (prefix === 'chatMessageIncoming') {
  354. rule.component = 'ChatMessage'
  355. } else if (prefix === 'chatMessageOutgoing') {
  356. rule.component = 'ChatMessage'
  357. rule.variant = 'outgoing'
  358. } else if (prefix === 'panel') {
  359. rule.component = 'PanelHeader'
  360. } else if (prefix === 'topBar') {
  361. rule.component = 'TopBar'
  362. } else if (prefix === 'chatMessage') {
  363. rule.component = 'ChatMessage'
  364. } else if (prefix === 'poll') {
  365. rule.component = 'PollGraph'
  366. } else if (prefix === 'btn') {
  367. rule.component = 'Button'
  368. } else {
  369. rule.component = prefix[0].toUpperCase() + prefix.slice(1).toLowerCase()
  370. }
  371. return keys.map((key) => {
  372. if (!data.colors[key]) return null
  373. const leftoverKey = key.replace(prefix, '')
  374. const parts = (leftoverKey || 'Bg').match(/[A-Z][a-z]*/g)
  375. const last = parts.slice(-1)[0]
  376. let newRule = { directives: {} }
  377. let variantArray = []
  378. switch (last) {
  379. case 'Text':
  380. case 'Faint': // typo
  381. case 'Link':
  382. case 'Icon':
  383. case 'Greentext':
  384. case 'Cyantext':
  385. case 'Border':
  386. newRule.parent = rule
  387. newRule.directives.textColor = data.colors[key]
  388. newRule.directives.textAuto = 'no-auto'
  389. variantArray = parts.slice(0, -1)
  390. break
  391. default:
  392. newRule = { ...rule, directives: {} }
  393. newRule.directives.background = data.colors[key]
  394. variantArray = parts
  395. break
  396. }
  397. if (last === 'Text' || last === 'Link') {
  398. const secondLast = parts.slice(-2)[0]
  399. if (secondLast === 'Light') {
  400. return null // unsupported
  401. } else if (secondLast === 'Faint') {
  402. newRule.state = ['faint']
  403. variantArray = parts.slice(0, -2)
  404. }
  405. }
  406. switch (last) {
  407. case 'Text':
  408. case 'Link':
  409. case 'Icon':
  410. case 'Border':
  411. newRule.component = last
  412. break
  413. case 'Greentext':
  414. case 'Cyantext':
  415. newRule.component = 'FunText'
  416. newRule.variant = last.toLowerCase()
  417. break
  418. case 'Faint':
  419. newRule.component = 'Text'
  420. newRule.state = ['faint']
  421. break
  422. }
  423. variantArray = variantArray.filter(x => x !== 'Bg')
  424. if (last === 'Link' && prefix === 'selectedPost') {
  425. // selectedPost has typo - duplicate 'Post'
  426. variantArray = variantArray.filter(x => x !== 'Post')
  427. }
  428. if (prefix === 'popover' && variantArray[0] === 'Post') {
  429. newRule.component = 'Post'
  430. newRule.parent = { component: 'Popover' }
  431. variantArray = variantArray.filter(x => x !== 'Post')
  432. }
  433. if (prefix === 'selectedMenu' && variantArray[0] === 'Popover') {
  434. newRule.parent = { component: 'Popover' }
  435. variantArray = variantArray.filter(x => x !== 'Popover')
  436. }
  437. switch (prefix) {
  438. case 'btn':
  439. case 'input':
  440. case 'alert': {
  441. const hasPanel = variantArray.find(x => x === 'Panel')
  442. if (hasPanel) {
  443. newRule.parent = { component: 'PanelHeader' }
  444. variantArray = variantArray.filter(x => x !== 'Panel')
  445. }
  446. const hasTop = variantArray.find(x => x === 'Top') // TopBar
  447. if (hasTop) {
  448. newRule.parent = { component: 'TopBar' }
  449. variantArray = variantArray.filter(x => x !== 'Top' && x !== 'Bar')
  450. }
  451. break
  452. }
  453. }
  454. if (variantArray.length > 0) {
  455. if (prefix === 'btn') {
  456. newRule.state = variantArray.map(x => x.toLowerCase())
  457. } else {
  458. newRule.variant = variantArray[0].toLowerCase()
  459. }
  460. }
  461. if (newRule.component === 'Panel') {
  462. return [newRule, { ...newRule, component: 'MobileDrawer' }]
  463. } else if (newRule.component === 'Button') {
  464. const rules = [
  465. newRule,
  466. { ...newRule, component: 'Tab' },
  467. { ...newRule, component: 'ScrollbarElement' }
  468. ]
  469. if (newRule.state?.indexOf('toggled') >= 0) {
  470. rules.push({ ...newRule, state: [...newRule.state, 'focused'] })
  471. rules.push({ ...newRule, state: [...newRule.state, 'hover'] })
  472. rules.push({ ...newRule, state: [...newRule.state, 'hover', 'focused'] })
  473. }
  474. if (newRule.state?.indexOf('hover') >= 0) {
  475. rules.push({ ...newRule, state: [...newRule.state, 'focused'] })
  476. }
  477. return rules
  478. } else if (newRule.component === 'Badge') {
  479. if (newRule.variant === 'notification') {
  480. return [newRule, { component: 'Root', directives: { '--badgeNotification': 'color | ' + newRule.directives.background } }]
  481. } else {
  482. return [newRule]
  483. }
  484. } else if (newRule.component === 'TopBar') {
  485. return [newRule, { ...newRule, parent: { component: 'MobileDrawer' }, component: 'PanelHeader' }]
  486. } else {
  487. return [newRule]
  488. }
  489. })
  490. })
  491. const flatExtRules = extendedRules.filter(x => x).reduce((acc, x) => [...acc, ...x], []).filter(x => x).reduce((acc, x) => [...acc, ...x], [])
  492. return [generateRoot(), ...convertShadows(), ...convertRadii(), ...convertOpacity(), ...convertFonts(), ...flatExtRules]
  493. }