logo

pleroma-fe

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

theme2_to_theme3.js (14812B)


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