logo

pleroma-fe

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

theme_tab.js (23596B)


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