logo

pleroma-fe

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

theme_tab.js (23786B)


  1. import {
  2. rgb2hex,
  3. hex2rgb,
  4. getContrastRatioLayers,
  5. relativeLuminance
  6. } from 'src/services/color_convert/color_convert.js'
  7. import {
  8. getThemes
  9. } from 'src/services/style_setter/style_setter.js'
  10. import {
  11. newImporter,
  12. newExporter
  13. } from 'src/services/export_import/export_import.js'
  14. import {
  15. SLOT_INHERITANCE
  16. } from 'src/services/theme_data/pleromafe.js'
  17. import {
  18. CURRENT_VERSION,
  19. OPACITIES,
  20. getLayers,
  21. getOpacitySlot,
  22. DEFAULT_SHADOWS,
  23. generateColors,
  24. generateShadows,
  25. generateRadii,
  26. generateFonts,
  27. shadows2to3,
  28. colors2to3
  29. } from 'src/services/theme_data/theme_data.service.js'
  30. import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
  31. import { init } from 'src/services/theme_data/theme_data_3.service.js'
  32. import {
  33. getCssRules,
  34. getScopedVersion
  35. } from 'src/services/theme_data/css_utils.js'
  36. import ColorInput from 'src/components/color_input/color_input.vue'
  37. import RangeInput from 'src/components/range_input/range_input.vue'
  38. import OpacityInput from 'src/components/opacity_input/opacity_input.vue'
  39. import ShadowControl from 'src/components/shadow_control/shadow_control.vue'
  40. import FontControl from 'src/components/font_control/font_control.vue'
  41. import ContrastRatio from 'src/components/contrast_ratio/contrast_ratio.vue'
  42. import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
  43. import Checkbox from 'src/components/checkbox/checkbox.vue'
  44. import Select from 'src/components/select/select.vue'
  45. import Preview from './theme_preview.vue'
  46. // List of color values used in v1
  47. const v1OnlyNames = [
  48. 'bg',
  49. 'fg',
  50. 'text',
  51. 'link',
  52. 'cRed',
  53. 'cGreen',
  54. 'cBlue',
  55. 'cOrange'
  56. ].map(_ => _ + 'ColorLocal')
  57. const colorConvert = (color) => {
  58. if (color.startsWith('--') || color === 'transparent') {
  59. return color
  60. } else {
  61. return hex2rgb(color)
  62. }
  63. }
  64. export default {
  65. data () {
  66. return {
  67. themeV3Preview: [],
  68. themeImporter: newImporter({
  69. validator: this.importValidator,
  70. onImport: this.onImport,
  71. onImportFailure: this.onImportFailure
  72. }),
  73. themeExporter: newExporter({
  74. filename: 'pleroma_theme',
  75. getExportedObject: () => this.exportedTheme
  76. }),
  77. availableStyles: [],
  78. selected: '',
  79. selectedTheme: this.$store.getters.mergedConfig.theme,
  80. themeWarning: undefined,
  81. tempImportFile: undefined,
  82. engineVersion: 0,
  83. previewTheme: {},
  84. shadowsInvalid: true,
  85. colorsInvalid: true,
  86. radiiInvalid: true,
  87. keepColor: false,
  88. keepShadows: false,
  89. keepOpacity: false,
  90. keepRoundness: false,
  91. keepFonts: false,
  92. ...Object.keys(SLOT_INHERITANCE)
  93. .map(key => [key, ''])
  94. .reduce((acc, [key, val]) => ({ ...acc, [key + 'ColorLocal']: val }), {}),
  95. ...Object.keys(OPACITIES)
  96. .map(key => [key, ''])
  97. .reduce((acc, [key, val]) => ({ ...acc, [key + 'OpacityLocal']: val }), {}),
  98. shadowSelected: undefined,
  99. shadowsLocal: {},
  100. fontsLocal: {},
  101. btnRadiusLocal: '',
  102. inputRadiusLocal: '',
  103. checkboxRadiusLocal: '',
  104. panelRadiusLocal: '',
  105. avatarRadiusLocal: '',
  106. avatarAltRadiusLocal: '',
  107. attachmentRadiusLocal: '',
  108. tooltipRadiusLocal: '',
  109. chatMessageRadiusLocal: ''
  110. }
  111. },
  112. created () {
  113. const self = this
  114. getThemes()
  115. .then((promises) => {
  116. return Promise.all(
  117. Object.entries(promises)
  118. .map(([k, v]) => v.then(res => [k, res]))
  119. )
  120. })
  121. .then(themes => themes.reduce((acc, [k, v]) => {
  122. if (v) {
  123. return {
  124. ...acc,
  125. [k]: v
  126. }
  127. } else {
  128. return acc
  129. }
  130. }, {}))
  131. .then((themesComplete) => {
  132. self.availableStyles = themesComplete
  133. })
  134. },
  135. mounted () {
  136. this.loadThemeFromLocalStorage()
  137. if (typeof this.shadowSelected === 'undefined') {
  138. this.shadowSelected = this.shadowsAvailable[0]
  139. }
  140. },
  141. computed: {
  142. themeWarningHelp () {
  143. if (!this.themeWarning) return
  144. const t = this.$t
  145. const pre = 'settings.style.switcher.help.'
  146. const {
  147. origin,
  148. themeEngineVersion,
  149. type,
  150. noActionsPossible
  151. } = this.themeWarning
  152. if (origin === 'file') {
  153. // Loaded v2 theme from file
  154. if (themeEngineVersion === 2 && type === 'wrong_version') {
  155. return t(pre + 'v2_imported')
  156. }
  157. if (themeEngineVersion > CURRENT_VERSION) {
  158. return t(pre + 'future_version_imported') + ' ' +
  159. (
  160. noActionsPossible
  161. ? t(pre + 'snapshot_missing')
  162. : t(pre + 'snapshot_present')
  163. )
  164. }
  165. if (themeEngineVersion < CURRENT_VERSION) {
  166. return t(pre + 'future_version_imported') + ' ' +
  167. (
  168. noActionsPossible
  169. ? t(pre + 'snapshot_missing')
  170. : t(pre + 'snapshot_present')
  171. )
  172. }
  173. } else if (origin === 'localStorage') {
  174. if (type === 'snapshot_source_mismatch') {
  175. return t(pre + 'snapshot_source_mismatch')
  176. }
  177. // FE upgraded from v2
  178. if (themeEngineVersion === 2) {
  179. return t(pre + 'upgraded_from_v2')
  180. }
  181. // Admin downgraded FE
  182. if (themeEngineVersion > CURRENT_VERSION) {
  183. return t(pre + 'fe_downgraded') + ' ' +
  184. (
  185. noActionsPossible
  186. ? t(pre + 'migration_snapshot_ok')
  187. : t(pre + 'migration_snapshot_gone')
  188. )
  189. }
  190. // Admin upgraded FE
  191. if (themeEngineVersion < CURRENT_VERSION) {
  192. return t(pre + 'fe_upgraded') + ' ' +
  193. (
  194. noActionsPossible
  195. ? t(pre + 'migration_snapshot_ok')
  196. : t(pre + 'migration_snapshot_gone')
  197. )
  198. }
  199. }
  200. },
  201. selectedVersion () {
  202. return Array.isArray(this.selectedTheme) ? 1 : 2
  203. },
  204. currentColors () {
  205. return Object.keys(SLOT_INHERITANCE)
  206. .map(key => [key, this[key + 'ColorLocal']])
  207. .reduce((acc, [key, val]) => ({ ...acc, [key]: val }), {})
  208. },
  209. currentOpacity () {
  210. return Object.keys(OPACITIES)
  211. .map(key => [key, this[key + 'OpacityLocal']])
  212. .reduce((acc, [key, val]) => ({ ...acc, [key]: val }), {})
  213. },
  214. currentRadii () {
  215. return {
  216. btn: this.btnRadiusLocal,
  217. input: this.inputRadiusLocal,
  218. checkbox: this.checkboxRadiusLocal,
  219. panel: this.panelRadiusLocal,
  220. avatar: this.avatarRadiusLocal,
  221. avatarAlt: this.avatarAltRadiusLocal,
  222. tooltip: this.tooltipRadiusLocal,
  223. attachment: this.attachmentRadiusLocal,
  224. chatMessage: this.chatMessageRadiusLocal
  225. }
  226. },
  227. // This needs optimization maybe
  228. previewContrast () {
  229. try {
  230. if (!this.previewTheme.colors.bg) return {}
  231. const colors = this.previewTheme.colors
  232. const opacity = this.previewTheme.opacity
  233. if (!colors.bg) return {}
  234. const hints = (ratio) => ({
  235. text: ratio.toPrecision(3) + ':1',
  236. // AA level, AAA level
  237. aa: ratio >= 4.5,
  238. aaa: ratio >= 7,
  239. // same but for 18pt+ texts
  240. laa: ratio >= 3,
  241. laaa: ratio >= 4.5
  242. })
  243. const colorsConverted = Object.entries(colors).reduce((acc, [key, value]) => ({ ...acc, [key]: colorConvert(value) }), {})
  244. const ratios = Object.entries(SLOT_INHERITANCE).reduce((acc, [key, value]) => {
  245. const slotIsBaseText = key === 'text' || key === 'link'
  246. const slotIsText = slotIsBaseText || (
  247. typeof value === 'object' && value !== null && value.textColor
  248. )
  249. if (!slotIsText) return acc
  250. const { layer, variant } = slotIsBaseText ? { layer: 'bg' } : value
  251. const background = variant || layer
  252. const opacitySlot = getOpacitySlot(background)
  253. const textColors = [
  254. key,
  255. ...(background === 'bg' ? ['cRed', 'cGreen', 'cBlue', 'cOrange'] : [])
  256. ]
  257. const layers = getLayers(
  258. layer,
  259. variant || layer,
  260. opacitySlot,
  261. colorsConverted,
  262. opacity
  263. )
  264. // Temporary patch for null-y value errors
  265. if (layers.flat().some(v => v == null)) return acc
  266. return {
  267. ...acc,
  268. ...textColors.reduce((acc, textColorKey) => {
  269. const newKey = slotIsBaseText
  270. ? 'bg' + textColorKey[0].toUpperCase() + textColorKey.slice(1)
  271. : textColorKey
  272. return {
  273. ...acc,
  274. [newKey]: getContrastRatioLayers(
  275. colorsConverted[textColorKey],
  276. layers,
  277. colorsConverted[textColorKey]
  278. )
  279. }
  280. }, {})
  281. }
  282. }, {})
  283. return Object.entries(ratios).reduce((acc, [k, v]) => { acc[k] = hints(v); return acc }, {})
  284. } catch (e) {
  285. console.warn('Failure computing contrasts', e)
  286. return {}
  287. }
  288. },
  289. shadowsAvailable () {
  290. return Object.keys(DEFAULT_SHADOWS).sort()
  291. },
  292. currentShadowOverriden: {
  293. get () {
  294. return !!this.currentShadow
  295. },
  296. set (val) {
  297. if (val) {
  298. this.shadowsLocal[this.shadowSelected] = this.currentShadowFallback.map(_ => Object.assign({}, _))
  299. } else {
  300. delete this.shadowsLocal[this.shadowSelected]
  301. }
  302. }
  303. },
  304. currentShadowFallback () {
  305. return (this.previewTheme.shadows || {})[this.shadowSelected]
  306. },
  307. currentShadow: {
  308. get () {
  309. return this.shadowsLocal[this.shadowSelected]
  310. },
  311. set (v) {
  312. this.shadowsLocal[this.shadowSelected] = v
  313. }
  314. },
  315. themeValid () {
  316. return !this.shadowsInvalid && !this.colorsInvalid && !this.radiiInvalid
  317. },
  318. exportedTheme () {
  319. const saveEverything = (
  320. !this.keepFonts &&
  321. !this.keepShadows &&
  322. !this.keepOpacity &&
  323. !this.keepRoundness &&
  324. !this.keepColor
  325. )
  326. const source = {
  327. themeEngineVersion: CURRENT_VERSION
  328. }
  329. if (this.keepFonts || saveEverything) {
  330. source.fonts = this.fontsLocal
  331. }
  332. if (this.keepShadows || saveEverything) {
  333. source.shadows = this.shadowsLocal
  334. }
  335. if (this.keepOpacity || saveEverything) {
  336. source.opacity = this.currentOpacity
  337. }
  338. if (this.keepColor || saveEverything) {
  339. source.colors = this.currentColors
  340. }
  341. if (this.keepRoundness || saveEverything) {
  342. source.radii = this.currentRadii
  343. }
  344. const theme = {
  345. themeEngineVersion: CURRENT_VERSION,
  346. ...this.previewTheme
  347. }
  348. return {
  349. // To separate from other random JSON files and possible future source formats
  350. _pleroma_theme_version: 2, theme, source
  351. }
  352. },
  353. isActive () {
  354. const tabSwitcher = this.$parent
  355. return tabSwitcher ? tabSwitcher.isActive('theme') : false
  356. }
  357. },
  358. components: {
  359. ColorInput,
  360. OpacityInput,
  361. RangeInput,
  362. ContrastRatio,
  363. ShadowControl,
  364. FontControl,
  365. TabSwitcher,
  366. Preview,
  367. Checkbox,
  368. Select
  369. },
  370. methods: {
  371. loadTheme (
  372. {
  373. theme,
  374. source,
  375. _pleroma_theme_version: fileVersion
  376. },
  377. origin,
  378. forceUseSource = false
  379. ) {
  380. this.dismissWarning()
  381. if (!source && !theme) {
  382. throw new Error('Can\'t load theme: empty')
  383. }
  384. const version = (origin === 'localStorage' && !theme.colors)
  385. ? 'l1'
  386. : fileVersion
  387. const snapshotEngineVersion = (theme || {}).themeEngineVersion
  388. const themeEngineVersion = (source || {}).themeEngineVersion || 2
  389. const versionsMatch = themeEngineVersion === CURRENT_VERSION
  390. const sourceSnapshotMismatch = (
  391. theme !== undefined &&
  392. source !== undefined &&
  393. themeEngineVersion !== snapshotEngineVersion
  394. )
  395. // Force loading of source if user requested it or if snapshot
  396. // is unavailable
  397. const forcedSourceLoad = (source && forceUseSource) || !theme
  398. if (!(versionsMatch && !sourceSnapshotMismatch) &&
  399. !forcedSourceLoad &&
  400. version !== 'l1' &&
  401. origin !== 'defaults'
  402. ) {
  403. if (sourceSnapshotMismatch && origin === 'localStorage') {
  404. this.themeWarning = {
  405. origin,
  406. themeEngineVersion,
  407. type: 'snapshot_source_mismatch'
  408. }
  409. } else if (!theme) {
  410. this.themeWarning = {
  411. origin,
  412. noActionsPossible: true,
  413. themeEngineVersion,
  414. type: 'no_snapshot_old_version'
  415. }
  416. } else if (!versionsMatch) {
  417. this.themeWarning = {
  418. origin,
  419. noActionsPossible: !source,
  420. themeEngineVersion,
  421. type: 'wrong_version'
  422. }
  423. }
  424. }
  425. this.normalizeLocalState(theme, version, source, forcedSourceLoad)
  426. },
  427. forceLoadLocalStorage () {
  428. this.loadThemeFromLocalStorage(true)
  429. },
  430. dismissWarning () {
  431. this.themeWarning = undefined
  432. this.tempImportFile = undefined
  433. },
  434. forceLoad () {
  435. const { origin } = this.themeWarning
  436. switch (origin) {
  437. case 'localStorage':
  438. this.loadThemeFromLocalStorage(true)
  439. break
  440. case 'file':
  441. this.onImport(this.tempImportFile, true)
  442. break
  443. }
  444. this.dismissWarning()
  445. },
  446. forceSnapshot () {
  447. const { origin } = this.themeWarning
  448. switch (origin) {
  449. case 'localStorage':
  450. this.loadThemeFromLocalStorage(false, true)
  451. break
  452. case 'file':
  453. console.error('Forcing snapshot from file is not supported yet')
  454. break
  455. }
  456. this.dismissWarning()
  457. },
  458. loadThemeFromLocalStorage (confirmLoadSource = false, forceSnapshot = false) {
  459. const {
  460. customTheme: theme,
  461. customThemeSource: source
  462. } = this.$store.getters.mergedConfig
  463. if (!theme && !source) {
  464. // Anon user or never touched themes
  465. this.loadTheme(
  466. this.$store.state.instance.themeData,
  467. 'defaults',
  468. confirmLoadSource
  469. )
  470. } else {
  471. this.loadTheme(
  472. {
  473. theme,
  474. source: forceSnapshot ? theme : source
  475. },
  476. 'localStorage',
  477. confirmLoadSource
  478. )
  479. }
  480. },
  481. setCustomTheme () {
  482. this.$store.dispatch('setThemeV2', {
  483. customTheme: {
  484. ignore: true,
  485. themeFileVersion: this.selectedVersion,
  486. themeEngineVersion: CURRENT_VERSION,
  487. ...this.previewTheme
  488. },
  489. customThemeSource: {
  490. themeFileVersion: this.selectedVersion,
  491. themeEngineVersion: CURRENT_VERSION,
  492. shadows: this.shadowsLocal,
  493. fonts: this.fontsLocal,
  494. opacity: this.currentOpacity,
  495. colors: this.currentColors,
  496. radii: this.currentRadii
  497. }
  498. })
  499. },
  500. updatePreviewColors () {
  501. const result = generateColors({
  502. opacity: this.currentOpacity,
  503. colors: this.currentColors
  504. })
  505. this.previewTheme.colors = result.theme.colors
  506. this.previewTheme.opacity = result.theme.opacity
  507. },
  508. updatePreviewShadows () {
  509. this.previewTheme.shadows = generateShadows(
  510. {
  511. shadows: this.shadowsLocal,
  512. opacity: this.previewTheme.opacity,
  513. themeEngineVersion: this.engineVersion
  514. },
  515. this.previewTheme.colors,
  516. relativeLuminance(this.previewTheme.colors.bg) < 0.5 ? 1 : -1
  517. ).theme.shadows
  518. },
  519. importTheme () { this.themeImporter.importData() },
  520. exportTheme () { this.themeExporter.exportData() },
  521. onImport (parsed, forceSource = false) {
  522. this.tempImportFile = parsed
  523. this.loadTheme(parsed, 'file', forceSource)
  524. },
  525. onImportFailure (result) {
  526. this.$store.dispatch('pushGlobalNotice', { messageKey: 'settings.invalid_theme_imported', level: 'error' })
  527. },
  528. importValidator (parsed) {
  529. const version = parsed._pleroma_theme_version
  530. return version >= 1 || version <= 2
  531. },
  532. clearAll () {
  533. this.loadThemeFromLocalStorage()
  534. },
  535. // Clears all the extra stuff when loading V1 theme
  536. clearV1 () {
  537. Object.keys(this.$data)
  538. .filter(_ => _.endsWith('ColorLocal') || _.endsWith('OpacityLocal'))
  539. .filter(_ => !v1OnlyNames.includes(_))
  540. .forEach(key => {
  541. this.$data[key] = undefined
  542. })
  543. },
  544. clearRoundness () {
  545. Object.keys(this.$data)
  546. .filter(_ => _.endsWith('RadiusLocal'))
  547. .forEach(key => {
  548. this.$data[key] = undefined
  549. })
  550. },
  551. clearOpacity () {
  552. Object.keys(this.$data)
  553. .filter(_ => _.endsWith('OpacityLocal'))
  554. .forEach(key => {
  555. this.$data[key] = undefined
  556. })
  557. },
  558. clearShadows () {
  559. this.shadowsLocal = {}
  560. },
  561. clearFonts () {
  562. this.fontsLocal = {}
  563. },
  564. /**
  565. * This applies stored theme data onto form. Supports three versions of data:
  566. * v3 (version >= 3) - newest version of themes which supports snapshots for better compatiblity
  567. * v2 (version = 2) - newer version of themes.
  568. * v1 (version = 1) - older version of themes (import from file)
  569. * v1l (version = l1) - older version of theme (load from local storage)
  570. * v1 and v1l differ because of way themes were stored/exported.
  571. * @param {Object} theme - theme data (snapshot)
  572. * @param {Number} version - version of data. 0 means try to guess based on data. "l1" means v1, locastorage type
  573. * @param {Object} source - theme source - this will be used if compatible
  574. * @param {Boolean} source - by default source won't be used if version doesn't match since it might render differently
  575. * this allows importing source anyway
  576. */
  577. normalizeLocalState (theme, version = 0, source, forceSource = false) {
  578. let input
  579. if (typeof source !== 'undefined') {
  580. if (forceSource || source?.themeEngineVersion === CURRENT_VERSION) {
  581. input = source
  582. version = source.themeEngineVersion
  583. } else {
  584. input = theme
  585. }
  586. } else {
  587. input = theme
  588. }
  589. const radii = input.radii || input
  590. const opacity = input.opacity
  591. const shadows = input.shadows || {}
  592. const fonts = input.fonts || {}
  593. const colors = !input.themeEngineVersion
  594. ? colors2to3(input.colors || input)
  595. : input.colors || input
  596. if (version === 0) {
  597. if (input.version) version = input.version
  598. // Old v1 naming: fg is text, btn is foreground
  599. if (typeof colors.text === 'undefined' && typeof colors.fg !== 'undefined') {
  600. version = 1
  601. }
  602. // New v2 naming: text is text, fg is foreground
  603. if (typeof colors.text !== 'undefined' && typeof colors.fg !== 'undefined') {
  604. version = 2
  605. }
  606. }
  607. this.engineVersion = version
  608. // Stuff that differs between V1 and V2
  609. if (version === 1) {
  610. this.fgColorLocal = rgb2hex(colors.btn)
  611. this.textColorLocal = rgb2hex(colors.fg)
  612. }
  613. if (!this.keepColor) {
  614. this.clearV1()
  615. const keys = new Set(version !== 1 ? Object.keys(SLOT_INHERITANCE) : [])
  616. if (version === 1 || version === 'l1') {
  617. keys
  618. .add('bg')
  619. .add('link')
  620. .add('cRed')
  621. .add('cBlue')
  622. .add('cGreen')
  623. .add('cOrange')
  624. }
  625. keys.forEach(key => {
  626. const color = colors[key]
  627. const hex = rgb2hex(colors[key])
  628. this[key + 'ColorLocal'] = hex === '#aN' ? color : hex
  629. })
  630. }
  631. if (opacity && !this.keepOpacity) {
  632. this.clearOpacity()
  633. Object.entries(opacity).forEach(([k, v]) => {
  634. if (typeof v === 'undefined' || v === null || Number.isNaN(v)) return
  635. this[k + 'OpacityLocal'] = v
  636. })
  637. }
  638. if (!this.keepRoundness) {
  639. this.clearRoundness()
  640. Object.entries(radii).forEach(([k, v]) => {
  641. // 'Radius' is kept mostly for v1->v2 localstorage transition
  642. const key = k.endsWith('Radius') ? k.split('Radius')[0] : k
  643. this[key + 'RadiusLocal'] = v
  644. })
  645. }
  646. if (!this.keepShadows) {
  647. this.clearShadows()
  648. if (version === 2) {
  649. this.shadowsLocal = shadows2to3(shadows, this.previewTheme.opacity)
  650. } else {
  651. this.shadowsLocal = shadows
  652. }
  653. this.updatePreviewColors()
  654. this.updatePreviewShadows()
  655. this.shadowSelected = this.shadowsAvailable[0]
  656. }
  657. if (!this.keepFonts) {
  658. this.clearFonts()
  659. this.fontsLocal = fonts
  660. }
  661. },
  662. updateTheme3Preview () {
  663. const theme2 = convertTheme2To3(this.previewTheme)
  664. const theme3 = init({
  665. inputRuleset: theme2,
  666. ultimateBackgroundColor: '#000000',
  667. liteMode: true
  668. })
  669. this.themeV3Preview = getScopedVersion(
  670. getCssRules(theme3.eager),
  671. '#theme-preview'
  672. ).join('\n')
  673. }
  674. },
  675. watch: {
  676. currentRadii () {
  677. try {
  678. this.previewTheme.radii = generateRadii({ radii: this.currentRadii }).theme.radii
  679. this.radiiInvalid = false
  680. } catch (e) {
  681. this.radiiInvalid = true
  682. console.warn(e)
  683. }
  684. },
  685. shadowsLocal: {
  686. handler () {
  687. try {
  688. this.updatePreviewShadows()
  689. this.shadowsInvalid = false
  690. } catch (e) {
  691. this.shadowsInvalid = true
  692. console.warn(e)
  693. }
  694. },
  695. deep: true
  696. },
  697. fontsLocal: {
  698. handler () {
  699. try {
  700. this.previewTheme.fonts = generateFonts({ fonts: this.fontsLocal }).theme.fonts
  701. this.fontsInvalid = false
  702. } catch (e) {
  703. this.fontsInvalid = true
  704. console.warn(e)
  705. }
  706. },
  707. deep: true
  708. },
  709. currentColors () {
  710. try {
  711. this.updatePreviewColors()
  712. this.colorsInvalid = false
  713. } catch (e) {
  714. this.colorsInvalid = true
  715. console.warn(e)
  716. }
  717. },
  718. currentOpacity () {
  719. try {
  720. this.updatePreviewColors()
  721. } catch (e) {
  722. console.warn(e)
  723. }
  724. },
  725. selected () {
  726. this.selectedTheme = Object.entries(this.availableStyles).find(([k, s]) => {
  727. if (Array.isArray(s)) {
  728. return s[0] === this.selected
  729. } else {
  730. return s.name === this.selected
  731. }
  732. })[1]
  733. },
  734. selectedTheme () {
  735. this.dismissWarning()
  736. if (this.selectedVersion === 1) {
  737. if (!this.keepRoundness) {
  738. this.clearRoundness()
  739. }
  740. if (!this.keepShadows) {
  741. this.clearShadows()
  742. }
  743. if (!this.keepOpacity) {
  744. this.clearOpacity()
  745. }
  746. if (!this.keepColor) {
  747. this.clearV1()
  748. this.bgColorLocal = this.selectedTheme[1]
  749. this.fgColorLocal = this.selectedTheme[2]
  750. this.textColorLocal = this.selectedTheme[3]
  751. this.linkColorLocal = this.selectedTheme[4]
  752. this.cRedColorLocal = this.selectedTheme[5]
  753. this.cGreenColorLocal = this.selectedTheme[6]
  754. this.cBlueColorLocal = this.selectedTheme[7]
  755. this.cOrangeColorLocal = this.selectedTheme[8]
  756. }
  757. } else if (this.selectedVersion >= 2) {
  758. this.normalizeLocalState(this.selectedTheme.theme, 2, this.selectedTheme.source)
  759. }
  760. }
  761. }
  762. }